• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Python模块:subprocess

Python模块 彭东稳 6年前 (2018-09-19) 32492次浏览 已收录 0个评论

一、subprocess

在早期的 Python 版本中,我们主要是通过 os.system()、os.popen()、os.spawn() 等函数来执行命令行指令的,另外还有一个很少使用的 commands 模块。从 Python 2.4 开始, Python 引入 subprocess 模块来管理子进程,以取代一些旧模块的方法。 subprocess 不但可以调用外部的命令作为子进程,而且可以连接到子进程的 input/output/error 管道,获取相关的返回信息。

运行 Python 的时候,我们都是在创建并运行一个进程,像 Linux 进程那样,一个进程可以 fork 一个子进程,并让这个子进程 exec 另外一个程序。在 Python 中,我们通过标准库中的 subprocess 模块来 fork 一个子进程,并运行一个外部的程序。

subprocess 模块中定义有多个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外 subprocess 还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

二、subprocess.run()

先来看看 subprocess.run() 方法,这也是使用较多的方法。在 Python 3.5 版本中添加,官方文档中提倡通过 subprocess.run() 方法替代其他函数来使用 subproccess 模块的功能。如果需要更高级的用例,可以直接使用基础 Popen 接口。

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)

运行 args 描述的命令,等待命令完成,然后返回 “CompletedProcess” 实例。

上面显示的参数只是最常见的参数,下面在常用参数中进行了描述。

  • capture_output

如果 capture_output 为 true,则将捕获 stdout 和 stderr。将使用 stdout = PIPE 和 stderr = PIPE 自动创建内部 Popen 对象。stdout 和 stderr 参数也可能无法使用。

  • timeout

很多脚本运行时会卡住,导致调用脚本一直等待,这很显然不是我们想看到的,因此执行命令的超时 Timeout 设置很有必要。好在 subprocess.run() 提供了 timeout 参数,当我们设置 timeout=30 时,表示如果在30秒内无法执行完毕,会抛出异常。

我们也可以使用 try、except 捕捉这个异常:

其超时参数传递给Popen.communicate()。如果超时,子进程将被杀死并等待。子进程终止后,将重新引发 “TimeoutExpired” 异常。

  • input

输入参数传递给 Popen.communicate() ,从而传递给子进程的 stdin。如果使用则必须是 bytes 序列,如果指定了编码或错误或文本为真, 则为字符串。使用时,将使用 stdin = PIPE 自动创建内部 Popen 对象,并且也可能无法使用 stdin 参数。

  • check

如果 check 为 true,并且进程退出时具有非0退出码,则将引发CalledProcessError异常。该异常的属性包含参数、退出码及 stdout 和 stderr,如果能捕获到。

  • encoding、error、text

如果指定了 encoding 或 error 或 text=True,则使用指定的 encoding 和 error 或 io.TextIOWrapper 以文本模式打开 stdin、stdout 和 stderr 的文件对象。默认值 universal_newlines 参数等效于文本,是为向后兼容而提供的。默认情况下,文件对象以二进制模式打开。

示例:

一种通用方式,把 subprocess.PIPE 赋值给 stdout 和 stderr 属性:

subprocess.run() 返回了一个 res 对象,其中包含了命令运行参数,运行返回状态码和输出内容。如下格式:

所以我们可以很轻松可以获取到相关属性信息,比如通过 res.args 属性获取执行的命令和参数,通过 res.returncode 属性获取命令执行结果状态码(当命令执行完成后才可以获取到状态码,其中0表示命令执行成功,1-255都表示命令执行失败),通过 res.stdout 属性获取标准输出信息,通过 res.stderr 属性获取错误输出信息等。这些属性的重要性不言而喻。

我们可以看到,上面输出的结果是 b’xxxxx’,这是一个 bytes 类型的数据。实际使用中需要将其转换为字符串格式。

既然转换成字符串了,那么自然就可以使用字符串相关的方法了。或者直接带上 universal_newlines=True ,这样输出就直接转成字符串了。

另外也可以直接把标准输出实时写入到文件中,如下代码:

同样的方法可以用在 Popen 类上。

  • shell

shell 默认为 False。在 Linux 下,shell=False 时,如果 args 是字符串,那么只能是命令,不能包含任何参数,否则报错;如果 args 是一个列表 list ,则 args 的第一项是定义程序命令字符串,其它项是调用系统 Shell 时的附加参数。

shell=True 时,如果 args 是字符串,Popen 对象是直接调用系统的 shell 来执行,字符串格式和 shell 终端书写格式一样;如果 args 是一个列表 list,则 args 的第一项是定义程序命令字符串,其它项是调用系统 shell 时的附加参数。官方推荐 shell=True 时,使用字符串方式传递参数。

如果想使用 shell 中的管道,重定向,文件通配符,环境变量等功能,例如 ”ifconfig | grep eth0 > mm”,那么只能使用 shell=True,并且使用字符串来传递。

综上, shell=True 功能最强大的,但因为强大也存在安全风险,需要谨慎的对待传递的参数。特别是当参数来自于用户输入时。 这时候可以使用 shlex.quote() 函数来将参数正确的用双引用引起来。

New in version 3.5.

Changed in version 3.6: Added encoding and errors parameters

Changed in version 3.7: Added the text parameter, as a more understandable alias of universal_newlines. Added the capture_output parameter.

三、subprocess.CompletedProcess

从 run() 执行完成后返回的对象,表示已完成的进程。

return CompletedProcess(process.args, retcode, stdout, stderr)

args

用于启动进程的参数,这可能是列表或字符串。

returncode

子进程的退出状态。通常,退出状态为0表示它已成功运行。负值 -n 表示子进程已被信号 n 终止 (仅限posix)。

stdout

从子进程中捕获的 stdout ,bytes 序列,或字符串。如果 run() 调用 encoding、errors or text = True。如果没有捕获 stdout,则为 ”None“。

如果你使用 stderr=subprocess.STDOUT 运行该进程。stdout、stderr 将在此属性中组合,stderr 将为 “None”。

stderr

从子进程中捕获的 stderr,bytes 序列,或字符串。如果 run() 调用 encoding、errors or text = True。如果没有捕获 stderr,则为 ”None“。

check_returncode()

如果返回代码为非0,则引发 calledtprocess 异常。

四、subprocess.Popen

实际上,上面的几个函数都是基于 Popen 对象的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向 Popen 类,该类生成的对象用来代表子进程。与上面的封装不同,Popen 对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的 wait() 方法,父进程才会等待(也就是阻塞)。

示例:

然后我们如果想重定向标准输出,需要使用while来循环判断 poll() 方法返回值,如下:

另外也可定义输出方式为文件等。

如果想重定向错误输出,同样也是判断 poll() 方法返回值,如下:

其中 poll 方法只有 Popen 对象才有(由于 run 方法是同步的所以不需要 poll 方法),其返回值不为 None 表示命令执行完成了,而返回值不为0表示命令执行失败,poll 方法内部返回的也是 popen.returncode 值,在 Linux 中通过 $? 也是可以获取命令执行成功与否的状态码,0表示执行成功,其他状态码均表示执行失败。

注意点:

run() 方法执行命令时必须等待执行完成才可以得到执行结果,而 Popen 对象类似异步执行,父进程无须等待子进程执行完成后才返回结果,父进程执行完就可以去通过 while 获取执行结果。他们的区别决定你使用该如何正确使用。

另外,run() 方法返回的标准输出是 bytes 类型,直接可以 decode。而 Popen 返回的是 _io.BufferedReader 对象,是一个流,可以使用 read 或者 readlines 方法解析,关于 IO 可以看看官方说明。

Popen类的实例具有以下方法:

Popen.poll()

检查子进程是否已终止。设置并返回 returncode 属性。否则,返回None。

Popen.wait(timeout=None)

等待子进程终止。设置并返回 returncode 属性。

如果进程在超时时间后没有终止,则引发 TimeoutExpired 异常。捕获此异常并重试等待则是安全的。

Popen.communicate(input=None, timeout=None)

与进程交互:将数据发送到 stdin,从 stdout 和 stderr 读取数据,直到达到文件结尾。等待进程终止。可选的 input 参数应该是要发送到子进程的数据,如果没有数据发送给子进程,则应该是None。如果在文本模式下打开流,则输入必须是字符串。否则,它必须是字节。

communicate() 返回一个元组(stdout_data,stderr_data)。如果在文本模式下打开流,则数据将是字符串;否则将是字节。

另外请注意,如果要将数据发送到进程的 stdin,则需要使用 stdin=PIPE 创建 Popen 对象。同样,要在结果元组中获取除 None 之外的任何内容,你还需要提供 stdout=PIPE 和 stderr=PIPE。

如果进程在超时后没有终止,则会引发 TimeoutExpired 异常。捕获此异常并重试通信则不会丢失任何输出。

如果超时到期,则子进程不会被终止,因此为了正确清理,行为良好的应用程序应该主动终止子进程并完成通信:

注意,读取的数据缓冲在内存中,因此如果数据很大或不受限制,请不要使用此方法。

Popen.send_signal(signal)

发送信号给子进程。

Popen.terminate()

停止子进程,在Posix操作系统上,该方法将 SIGTERM 信号发送给子进程。在Windows上,调用Win32 API函数 TerminateProcess() 来停止子进程。

Popen.kill()

杀死子进程,在Posix操作系统上,该函数将 SIGKILL 信号发送给子进程。在Windows上,kill() 是 terminate() 的别名。

Popen.args

返回被执行的命令和选项,是一个列表或字符串。

Popen.stdin

如果 stdin 参数是 PIPE ,则此属性是 open() 方法返回的可写流对象。如果指定了 encoding 或 errors 参数或者 universal_newlines=True,则流是文本流,否则是字节流。如果 stdin 参数不是 PIPE ,则此属性为 None。

Popen.stdout

如果 stdout 参数是 PIPE,则此属性是 open() 方法返回的可读流对象。从流中读取子进程提供的标准输出。如果指定了 encoding 或 errors 参数或者 universal_newlines=True ,则流是文本流,否则是字节流。如果 stdout 参数不是 PIPE,则此属性为 None。

Popen.stderr

如果 stderr 参数是 PIPE,则此属性是 open() 方法返回的可读流对象。从流中读取子进程提供的错误输出。如果指定了 encoding 或 errors 参数或者 universal_newlines=True,则流是文本流,否则它是字节流。如果 stderr 参数不是 PIPE,则此属性为 None。

Popen.pid

子进程的进程ID。请注意,如果将 shell 参数设置为 True,则这是生成的 shell 的进程ID。

Popen.returncode

子进程返回码,由 poll() 和 wait() 设置(间接通过communic())。 “None”值表示该进程尚未终止。

四、subprocess.PIPE

可以用作 Popen 的 stdin,stdout or stderr 参数的特殊值,表示打开标准流的管道。最适用于 Popen.communicate() 。

可以在 Popen 对象建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用 subprocess.PIPE 将多个子进程的输入和输出连接在一起,构成管道(pipe),如下例子:

subprocess.PIPE 实际上为文本流提供一个缓存区。child1 的 stdout 将文本输出到缓存区,随后 child2 的 stdin 从该PIPE中将文本读取走。 child2 的输出文本也被存放在 PIPE 中,直到 communicate() 方法从 PIPE 中读取出 PIPE 中的文本。

注意:communicate() 方法是 Popen 对象的一个方法,该方法会阻塞父进程,直到子进程完成。

subprocess 模块对于依赖TTY的外部命令不合适用。 例如,你不能使用它来自动化一个用户输入密码的任务(比如一个ssh会话)。 这时候,你需要使用到第三方模块了,比如基于著名的 expect家族的工具(pexpect或类似的)。

<参考>

https://www.jianshu.com/p/8e582146bd4c

https://mp.weixin.qq.com/s/2xm_rNb2Vqyb61JKUoHrAg

http://blog.chinaunix.net/uid-23504396-id-4661783.html

http://www.10tiao.com/html/160/201708/2649640336/1.html#collapseOne

https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (3)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!