python子进程模块subprocess详解与应用实例 之一

subprocess--子进程管理器

一、subprocess 模块简介

subprocess最早是在2.4版本中引入的。
subprocess模块用来生成子进程,并可以通过管道连接它们的输入/输出/错误,以及获得它们的返回值。
它用来代替多个旧模块和函数:
os.system
os.spawn*
os.popen*
popen2.*
commands.*

关于这个模块可以取代的旧函数可以参见 subprocess-replacements 一节。
POSIX用户(Linux, BSD, etc)还可以安装和使用更新的subprocess32模块来代替python 2.7版本中的subprocess.
subprocess32虽然是一个低版本,但在有些情况下效果更好。

1.1. 使用 subprocess模块

启动子进程的推荐方式是使用下面的便利功能。
当这些还不能满足需求时,就需要使用底层的Popen接口。

1. subprocess.call

语法:
     subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)
语义:
     运行由args指定的命令,直到命令结束后,返回 返回码的属性值。

上面的参数是最常见的方式,下面是示例代码:
>>>
>>> subprocess.call(["ls", "-l"])
0
>>> subprocess.call("exit 1", shell=True)
1
WARNING: 使用 shell=True 是一种安全保护机制。
NOTE: 在使用这个函数时,不要使用 stdout=PIPE 或 stderr=PIPE 参数,
      不然会导致子进程输出的死锁。
      如果要使用管道,可以在 communicate()方法中使用Popen
示例代码:
import subprocess
rc = subprocess.call(["ls","-l"])

可以通过一个shell来解释一整个字符串:
import subprocess
out = subprocess.call("ls -l", shell=True)
out = subprocess.call("cd ..", shell=True)

使用了shell=True这个参数。
这个时候,我们使用一整个字符串,而不是一个表来运行子进程。
Python将先运行一个shell,再用这个shell来解释这整个字符串。

shell命令中有一些是shell的内建命令,这些命令必须通过shell运行,$cd。
shell=True允许我们运行这样一些命令。

2. subprocess.check_call

语法: 
     subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)
语义:
     运行由args指定的命令,直到命令执行完成。
     如果返回码为零,则返回。否则,抛出 CalledProcessError异常。
     CalledProcessError对象包含有返回码的属性值。

上面显示的参数仅仅是最常见的,下面是用户更常用的参数。
示例代码如下:
>>>
>>> subprocess.check_call(["ls", "-l"])
0
>>> subprocess.check_call("exit 1", shell=True)
Traceback (most recent call last):
   ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

这个函数在python 2.5版本中引入。
WARNING: 使用 shell=True 是一种安全机制。
NOTE: 不要在这个函数中使用 stdout=PIPE 或 stderr=PIPE, 否则会造成子进程死锁。
      如果需要使用管道,可以在 communicate()方法中使用Popen.

3. subprocess.check_output

语法: 
      subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)
语义:
     运行args定义的命令,并返回一个字符串表示的输出值。
     如果返回码为非零,则抛出 CalledProcessError异常。
示例代码:
>>>
>>> subprocess.check_output(["echo", "Hello World!"])
'Hello World!\n'
>>> subprocess.check_output("exit 1", shell=True)
Traceback (most recent call last):
   ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
如果要捕捉结果中的标准错误,使用 stderr=subprocess.STDOUT参数:
>>>
>>> subprocess.check_output(
...     "ls non_existent_file; exit 0",
...     stderr=subprocess.STDOUT,
...     shell=True)
'ls: non_existent_file: No such file or directory\n'
这个函数在python 2.7版本中引入。
WARNING: 使用 shell=True 是一种安全机制。
NOTE: 不要在这个函数中使用 stdout=PIPE 或 stderr=PIPE, 否则会造成子进程死锁。
      如果需要使用管道,可以在 communicate()方法中使用Popen.

4. subprocess.PIPE

   使用Popen时,用于 stdin, stdout和stderr参数的特殊值,表示打开连接标准流的管道。

5. subprocess.STDOUT

   使用Popen时,用于 stderr 参数的特殊值,表示将标准错误重定向到标准输出的同一个句柄。

6. 异常 subprocess.CalledProcessError

   当由 check_call()或 check_output()运行的进程返回非零状态值时抛出的异常。

7. returncode

   子进程的退出状态。

8. cmd

   子进程执行的命令。

9. output

   如果check_output()抛出异常时,子进程的输出值。
   否则,没有这个值。

1.1.1. 常用的参数

为了支持各种用户使用情况 ,Popen构建函数接收多种可选参数。
对于最典型的情况,许多参数都保留有安全的默认值,这些最常用的方式如下:

1. args

所有的函数都需要这个参数,并且它是一个字符串,或者是程序的参数序列。
提供一个参数序列是更推荐的方式,因为这样能允许模块接收空格 或 引号中的参数。
如果传递的是单个字符串,要么 shell=True, 或都要么 字符串就程序名字,并且不能带参数。

2. stdin, stdout 和 stderr

stdin, stdout和stderr指定了执行程序的标准输入,标准输出和标准错误的文件句柄。
它们的值可以是PIPE, 一个存在的文件描述符(正整数),一个存在的文件对象,或 None.
PIPE 表示创建一个连接子进程的新管道。
默认值 为 None, 表示不做重定向。
子进程的文件句柄可以从父进程中继承得到。
另外,stderr可以设置值为 STDOUT,表示子进程的错误数据可以和标准输出是同一个文件句柄。

当stdout 或 stderr的值为管道 并且  universal_newlines的值为真时,
对于以 ‘U'模式参数打开的新行,所有行的结束都会转换成'\n'。

3. shell

如果 shell的值为 True, 则指定的命令行会通过shell来执行。
如果你使用Python来作为流程控制,那这样的设置会很有用,因为它提供了绝大多数的系统shell命令且可以很方便地使用
shell的各种功能,如 shell 管道,文件名通配符,环境变量扩展,以及用户目录扩展符 ~。
但是,需要注意的是,Python 提供了类似shell功能的实现。

WARNING: 执行不受信任来源的shell命令会是一个严重的安全问题。
         基于这一点,shell=True 是不建议的。
示例代码如下:
>>>
>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / #
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

shell=False 关闭了shell的所有基本功能 ,从而不会有上面所说的安全漏洞。
可以在Popen构建函数的帮助文档中看到,它只有在 shell=False时才能工作。
当使用  shell=True时,pipes.quote()可以被用于转译空格,shell的字符等。

1.1.2. Popen构建函数

subprocess中更底层的进程创建和管理可以通过Popen类实现。
它提供了更多的灵活性,程序员通过它能处理更多复杂的情况。
语法:
     class subprocess.Popen(args, bufsize=0, executable=None, 
                            stdin=None, stdout=None, stderr=None, 
                            preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None,
                            universal_newlines=False, startupinfo=None, creationflags=0)
语义:
     在新进程中执行一个子程序。
     在Unix中,这个类使用 类似于 os.execvp()方式来执行子程序。
     在Windows中,这个类使用Windows的 CreateProcess()函数来执行子程序。
参数解析:
args: 一个程序参数序列,或者单个字符串。
      默认的,要执行的程序应该是序列的第一个字段。
      如果单个字符串,它的解析依赖于平台
在Unix中,如果 args是一个字符串,那么这个字符串解释成被执行程序的名字或路径。
然而,这种情况只能用在不需要参数的程序。

NOTE: 当对args确定了正确的分隔符后,shlex.split()就很有用,特别是在复杂的情况下:
>>>
>>> import shlex, subprocess
>>> command_line = raw_input()
/bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'"
>>> args = shlex.split(command_line)
>>> print args
['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"]
>>> p = subprocess.Popen(args) # Success!

NOTE: 选项(如 -input) 和 参数(如 eggs.txt) 在shell中是用空格分隔成分离的列表元素。
      如果参数需要引号或反斜线,则它们会是一个单一列表元素。
shell参数(默认值为False)声明了是否使用shell来执行程序。
如果 shell=True, 它将args看作是一个字符串,而不是一个序列。

在Unix系统,且 shell=True时,shell默认使用 /bin/sh.
如果 args是一个字符串,则它声明了通过shell执行的命令。这意味着,字符串必须要使用正确的格式。
如果 args是一个序列,则第一个元素就是命令字符串,而其它的元素都作为参数使用。
可以这样说,Popen等价于:
      Popen(['/bin/sh', '-c', args[0], args[1], ...])
bufsize: 如果指定了值,则它和内建函数 open()对应的参数有相同的意义:
         0 -- 表示不缓冲
         1 -- 表示缓冲
         任何其它的正数值表示buffer的大小。
         负数值表示使用系统默认值,通常表示完全缓冲。
         它的默认值为零。
NOTE: 如果遇到性能问题,建议将bufsize设置成 -1 或足够大的正数(如 4096)。

executable: 指定了用于代替执行的程序。它极少会用到。
stdin, stdout, stderr:指定了执行程序的标准输入,标准输出和标准错误的文件句柄。
         有效的值可以是 PIPE, 一个存在的文件描述符,或存在的文件对象,或 None.
         默认值为 None。 
         stderr可以设置成STDOUT, 它表示将子进程的stderr数据重定向到stdout.
preexec_fn: 如果它被设置成可调用对象,那么这个对象会在子进程执行前被子进程调用,只用于Unix.
close_fds:  如果设置为True, 则在子进程被执行前,除0,1和2之外的所有文件描述符都将被关闭,只用于Unix。
cwd: 当它不为 None时,子程序在执行前,它的当前路径会被替换成 cwd的值。
     这个路径并不会被添加到可执行程序的搜索路径,所以cwd不能是相对路径。
env: 当它不为 None时,它是新进程的环境变量的映射。
     可以用它来代替当前进程的环境。 
universal_newlines: 为真时,文件对象 stdout和 stderr都被以文本文件的方式打开

示例代码:
1. Popen对象创建后,主程序不会自动等待子进程完成。
我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block):
    import subprocess
    child = subprocess.Popen(["ping","-c","5","www.google.com"])
    print("parent process")

从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。

2. 对比等待的情况:
   import subprocess
   child = subprocess.Popen(["ping","-c","5","www.google.com"])
   child.wait()
   print("parent process")

此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的child对象:
child.poll()           # 检查子进程状态
child.kill()           # 终止子进程
child.send_signal()    # 向子进程发送信号
child.terminate()      # 终止子进程
子进程的PID存储在child.pid

3. 可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,
并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):
    import subprocess
    child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
    child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
    out = child2.communicate()
    print(out)

subprocess.PIPE实际上为文本流提供一个缓存区。
child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。
child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。
要注意的是,communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。

4. 还可以利用communicate()方法来使用PIPE给子进程输入:
    import subprocess
    child = subprocess.Popen(["cat"], stdin=subprocess.PIPE)
    child.communicate("vamei")
我们启动子进程之后,cat会等待输入,直到我们用communicate()输入"vamei"。

通过使用subprocess包,我们可以运行外部程序。这极大的拓展了Python的功能。
如果你已经了解了操作系统的某些应用,你可以从Python中直接调用该应用(而不是完全依赖Python),
并将应用的结果输出给Python,并让Python继续处理。
shell的功能(比如利用文本流连接各个应用),就可以在Python中实现。

1.1.3.异常

在开始执行新程序之前,子进程抛出的异常,会被重新抛出到父进程。
另外,异常对象会有一个额外的属性,叫做 child_traceback, 它是一个字符串,包含从子程序的观察点追踪到的信息。

最常见的抛出的异常是 OSError, 当它发生时,通常是我们执行了一个不存在的文件。应用程序应当要能处理这个异常。
如果使用无效的参数调用 Popen,会抛出 ValueError异常。
如果被调用进程的返回码不为零,则check_call()和check_output()会抛出 CalledProcessError异常。

1.1.4. 安全

Unlike some other popen functions, this implementation will never call a system shell implicitly. 
This means that all characters, including shell metacharacters, can safely be passed to child processes. 
Obviously, if the shell is invoked explicitly, then it is the application’s responsibility to ensure that 
all whitespace and metacharacters are quoted appropriately.

posted @ 2017-06-01 22:41  崔崔0624  阅读(619)  评论(0编辑  收藏  举报
TOP