一、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 捕捉这个异常:
res = subprocess.run('/bin/bash /tmp/for.sh',stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, timeout=30)
print('code: ',res.returncode,'stdout: ',res.stdout)
except subprocess.TimeoutExpired as e:
print(e)
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.run(["ls", "-l"]) # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)
>>> subprocess.run("exit 1", shell=True, check=True) # CallProcessError
Traceback (most recent call last):
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
>>> subprocess.run(["ls", "-l", "/dev/null"], capture_output=True) # capture output
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')
>>>
subprocess
.run
(
[
"ls"
,
"-l"
]
)
# doesn't capture output
CompletedProcess
(
args
=
[
'ls'
,
'-l'
]
,
returncode
=
0
)
>>>
subprocess
.run
(
"exit 1"
,
shell
=
True
,
check
=
True
)
# CallProcessError
Traceback
(
most
recent
call
last
)
:
.
.
.
subprocess
.CalledProcessError
:
Command
'exit 1'
returned
non
-
zero
exit
status
1
>>>
subprocess
.run
(
[
"ls"
,
"-l"
,
"/dev/null"
]
,
capture_output
=
True
)
# capture output
CompletedProcess
(
args
=
[
'ls'
,
'-l'
,
'/dev/null'
]
,
returncode
=
0
,
stdout
=
b
'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n'
,
stderr
=
b
''
)
所以我们可以很轻松可以获取到相关属性信息,比如通过 res.args 属性获取执行的命令和参数,通过 res.returncode 属性获取命令执行结果状态码(当命令执行完成后才可以获取到状态码,其中0表示命令执行成功,1-255都表示命令执行失败),通过 res.stdout 属性获取标准输出信息,通过 res.stderr 属性获取错误输出信息等。这些属性的重要性不言而喻。
我们可以看到,上面输出的结果是 b’xxxxx’,这是一个 bytes 类型的数据。实际使用中需要将其转换为字符串格式。
stdout = open('/tmp/subprocess_stdout', 'wb')
stderr = open('/tmp/subprocess_stderr', 'wb')
popen = subprocess.run(['ping','www.baidu.com'], stdout=stdout.fileno(), stderr=stderr.fileno())
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)
用于启动进程的参数,这可能是列表或字符串。
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 res.poll() is None:
out = res.stdout.readline().decode()
with open('/tmp/all.log', 'a+') as f:
f.write(out)
其中 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.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),如下例子:
>>> import subprocess
>>> child1 = subprocess.Popen(["cat", "/etc/passwd"], stdout=subprocess.PIPE)
>>> child2 = subprocess.Popen(["grep", "0:0"], stdin=child1.stdout, stdout=subprocess.PIPE)
>>> print(child2.communicate())
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
)
赏
版权声明
本站的文章和资源来自互联网或者站长
的原创,按照 CC BY -NC -SA 3.0 CN
协议发布和共享,转载或引用本站文章
应遵循相同协议。如果有侵犯版权的资
源请尽快联系站长,我们会在24h内删
除有争议的资源。
Linux
Database
Network
Python
Ansible中文权威
运维进行时
DB-engines
Python 3
MySQL Blog
Redis中文指南
Redis命令手册
运维那点事
最新评论