python模块之subprocess

来源:互联网 发布:淘宝帽子女士 编辑:程序博客网 时间:2024/05/16 23:37
前言: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异常。


_______________________________________

下面介绍subprocess的Popen方法,这是上面讲的方法的底层实现,当上面方法不能满足需求时可以用Poen来实现.

1.2. Popen 对象

Popen类的实例有下列方法:

1. Popen.poll()

检查子进程是否已经结束,设置并返回返回码值。

2. Popen.wait()

等待子进程结束,设置并返回返回码值。
WARNING: 当使用 stdout=PIPE 或 stderr=PIPE 并且子进程生成了足够多的输出信息到管道,以至于管道阻塞,将会造成死锁。
         使用 communicate()可以避免这种情况。

3. Popen.communicate(input=None)

和子进程进行交互: 发送数据到stdin, 从stdout和 stderr读取数据,直到遇到 end-of-file。等待子进程结束。
可选的 input 参数是一个传送给子进程的字符串,或 None。
communicate() 返回一个元组(stdoutdata, stderrdata).
NOTE: 如果想发送数据到子进程的 stdin, 需要设置 stdin=PIPE并创建Popen对象。
      相似的,要想获得返回结果元组中的非空值,需要设置 stdout=PIPE或 stderr=PIPE.
      实际上,读取的数据是缓冲在内存中,因此,如果数据太大或无限时不要使用这个上方法。

4. Popen.send_signal(signal)

发送一个信号 signal到子进程。
NOTE: 在Windows中, SIGTERM是terminate()的别名。

5. Popen.terminate()

停止子程序,在Posix操作系统中,这个方法发送的是SIGTERM到子程序。

6. Popen.kill()

杀死子进程,在Posix操作系统中,这个上函数发送SIGKILL给子进程。