🚀 个人主页:xmp65535
🚀 专栏:python技术专栏
目录
6. subprocess.getstatusoutput()
在实际开发过程中,我们经常会遇到需要从Python脚本中调用外部程序或脚本的场景。这时候,Python的
subprocess
模块就显得尤为重要。它是Python用来生成子进程、连接到它们的输入/输出/错误管道,以及获取它们的返回码的一个模块。本文将带你深入了解subprocess
模块,学会如何高效地使用它来扩展你的Python脚本功能。
subprocess
模块是Python标准库的一部分,它为创建和管理子进程提供了强大的接口。在Python 2.4中引入以取代旧的模块(如os.system
和os.spawn*
等),并提供了更复杂的进程管理功能,比如超时控制、I/O管道通信等。
- 安全性:与
os.system
相比,subprocess
避免了shell注入攻击的风险。- 灵活性:
subprocess
可以与子进程的stdin、stdout和stderr流进行交互。- 功能丰富:它支持复杂的系统调用,如管道和重定向。
subprocess.run()
是 Python 3.5 引入的一个高级接口,用于替代旧的 subprocess.call()
和 subprocess.check_output()
等函数。CompletedProcess
对象,包含执行结果的信息,如返回码、标准输出和标准错误等。subprocess.run()
提供了更多的参数选项,如可以指定输入数据、设置超时时间、指定工作目录等。参数说明:
args
:这是命令和参数的列表,其中命令是列表的第一个元素。如果shell
参数设置为True,args必须是一个字符串
stdin
:可以是None
、一个已打开的文件描述符、一个现有的文件对象,或者subprocess.PIPE
。用于指定子进程的标准输入。
input
:如果stdin
参数是PIPE
,这个参数可以被用来传递一个字符串到子进程的标准输入。
stdout
和stderr
:这些参数与stdin
类似,但它们控制的是标准输出和标准错误输出。它们也可以被设置为None
、一个文件描述符、一个现有的文件对象,或者subprocess.PIPE
。
capture_output
:如果设置为True
,stdout
和stderr
将会被捕获。这相当于设置stdout=subprocess.PIPE
和stderr=subprocess.PIPE
。
shell
:如果为True
,指定的命令将通过 shell 执行。
cwd
:如果指定,子进程的当前工作目录将被改变到cwd
。
timeout
:如果指定,且执行时间超过了这个值(以秒计),将会抛出subprocess.TimeoutExpired
异常。
check
:如果设置为True
,并且进程以非零状态码退出,将会抛出subprocess.CalledProcessError
异常。
encoding
和errors
:指定如何解码从子进程输出的字节。只有当stdout
或stderr
被捕获时才有效。
text
:一个简写的标志,用于设置encoding='utf-8'
和universal_newlines=True
,它会将stdout
和stderr
的输出作为字符串处理。
env
:指定子进程的环境变量。如果为None
,使用父进程的环境变量。
universal_newlines
:如果为True
,stdin
、stdout
和stderr
将会作为文本流处理,类似于text=True
。
**other_popen_kwargs
:你可以提供其他的关键字参数,这些参数将直接传递给Popen
构造函数。
示例:
- import subprocess
-
- try:
- result = subprocess.run(['ls', '-l', '/nonexistent'], capture_output=True, text=True, check=True)
- print(result.stdout)
- except subprocess.CalledProcessError as e:
- print('Failed to run command:', e)
subprocess.Popen()
是一个更底层的接口,用于在新的进程中执行命令。Popen
对象,可以用于控制和管理子进程的执行过程,如向子进程发送输入数据、读取子进程的输出、等待子进程结束等。subprocess.Popen()
提供了更多的灵活性和控制。参数说明:
args:
和run
函数相同。bufsize
:缓冲区大小,-1 表示使用系统默认缓冲区大小,0 表示不使用缓冲区,1 表示行缓冲。executable
:如果指定,将使用这个可执行文件来替换要执行的程序。stdin
,stdout
,stderr
,shell
,cwd
:与run
函数相同。preexec_fn
:仅在 Unix 系统上有效)是一个可调用对象,它将在子进程运行之前被调用。close_fds
:如果为True
(默认值),在子进程中除了0
、1
和2
之外所有的文件描述符都将被关闭。env
:用于指定环境变量。
shell
:如果这个参数为True
,指定的命令将通过 shell 执行。
cwd
:如果指定,子进程的工作目录将改变到cwd
。
env
:用于指定子进程的环境变量。如果为None
,子进程会继承父进程的环境变量。
universal_newlines
:(现在推荐使用text
参数)如果为True
,stdin
、stdout
和stderr
将作为文本流(字符串)而不是字节流。
startupinfo
和creationflags
:这两个参数仅在 Windows 系统上有效,用于控制子进程的创建方式。
restore_signals
:(仅在 Unix 系统上有效)如果为True
,在执行新程序前,将会恢复 Python 的信号处理为默认值。
start_new_session
:(仅在 Unix 系统上有效)如果为True
,子进程将会在新的会话中启动。
pass_fds
:(仅在 Unix 系统上有效)要传递给子进程的文件描述符。
encoding
和errors
:这些参数用于设置在解码和编码文本数据时使用的编码和错误处理方式。
text
:一个用于设置编码为"utf-8"
且universal_newlines=True
的简化参数,这将会让stdout
和stderr
的输出作为字符串处理。
示例:
- import subprocess
-
- process = subprocess.Popen(['ping', '-c', '4', 'example.com'], stdout=subprocess.PIPE, text=True)
- stdout, stderr = process.communicate()
- print(stdout)
Popen
类有一些其他的方法供进阶使用,例如 poll()
, wait()
, communicate()
等。
poll()
:检查子进程是否已经结束,返回 returncode
属性。wait()
:等待子进程结束,并返回 returncode
属性。communicate(input=None, timeout=None)
:与子进程进行交互,发送数据到 stdin,并从 stdout 和 stderr 读取数据,然后等待子进程结束。示例:
- import subprocess
-
- process = subprocess.Popen(['sleep', '2'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- print(process.poll()) # None,因为进程还未结束
- output, error = process.communicate() # 等待进程结束,并获取输出
- print(process.poll()) # 返回码,通常为0,表示成功
subprocess.PIPE
subprocess.PIPE
可以被用作 Popen
函数的 stdin
/stdout
/stderr
参数的值,它表示需要创建一个新的管道到子进程。
示例:
- import subprocess
-
- # 创建新的管道,捕获输出
- with subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
- out, err = proc.communicate()
- print(out.decode())
这个函数用于执行指定的命令,等待命令执行完成,并返回命令的返回码。
参数 基本上与 subprocess.run()
相同。
示例:
- import subprocess
-
- return_code = subprocess.call(["echo", "Hello, World!"])
- print("Command returned:", return_code)
这个函数与 call()
类似,但如果执行的命令返回非0退出码,则会抛出 CalledProcessError
。
参数 与 subprocess.call()
相同。
示例:
- import subprocess
-
- try:
- subprocess.check_call(["false"]) # 这将抛出异常,因为 'false' 命令的退出码不为0
- except subprocess.CalledProcessError as e:
- print('Error:', e)
subprocess.check_output()
用于执行命令并返回其标准输出。- 如果命令执行失败或返回非零的返回码,则会抛出
CalledProcessError
异常。
参数 基本上与 subprocess.run()
相同。
示例:
- import subprocess
-
- try:
- output = subprocess.check_output(["echo", "Hello, World!"])
- print(output.decode()) # 输出需要解码
- except subprocess.CalledProcessError as e:
- print('Error:', e)
这个函数在 Unix 上执行带有{ cmd ; } 2>&1
的shell命令,并返回 (status, output)
元组。
示例:
- import subprocess
-
- status, output = subprocess.getstatusoutput('ls /nonexistent')
- print('Status:', status)
- print('Output:', output)
这个命令会尝试列出一个不存在的目录,因此它将返回一个错误状态和错误信息。
这个函数用于执行命令并获取它的输出(stdout),不返回返回码。
示例:
- import subprocess
-
- output = subprocess.getoutput('echo Hello, World!')
- print('Output:', output)
这个命令会打印 Hello, World!
到stdout,并捕获这个输出。
subprocess
模块允许你启动新的进程,连接到它们的输入/输出/错误管道,并获取它们的返回码。这个模块提供了很多与子进程进行交互的功能,Popen
类是其中的核心。使用 Popen
构造函数,你可以指定进程的输入、输出、错误管道,以及许多其他的选项。
例如,你可以使用 Popen
类并将 stdin
,stdout
,和 stderr
参数设置为 subprocess.PIPE
,这样就可以与子进程的输入和输出流进行交互:
- import subprocess
-
- # 启动进程
- proc = subprocess.Popen(['sort'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
- # 向子进程发送数据并获取输出
- output, errors = proc.communicate(input=b'one\ntwo\nthree\nfour\nfive\n')
-
- # 打印排序后的输出数据
- print(output.decode()) # 输出将会是排序后的列表
在这个例子中,communicate()
方法用于发送数据到子进程的 stdin,并从 stdout 和 stderr 获取数据。
在 Unix 和 Windows 系统中,可以使用 subprocess 模块创建管道。这通常是通过将一个进程的 stdout
与另一个进程的 stdin
相连来完成的。
- import subprocess
-
- # 创建一个管道:grep通过ps获取的进程列表
- ps = subprocess.Popen(('ps', 'aux'), stdout=subprocess.PIPE)
- grep = subprocess.Popen(('grep', 'python'), stdin=ps.stdout, stdout=subprocess.PIPE)
- ps.stdout.close() # 允许ps释放资源
- output = grep.communicate()[0]
-
- print(output.decode())
在这个例子中,ps
命令的输出被直接连接到了 grep
命令的输入。我们通过关闭 ps.stdout
来确保 ps
可以接收到 SIGPIPE 信号,如果 grep
先完成。
subprocess.run()
函数包括一个 timeout
参数,可以用来限制子进程的运行时间。如果超时,将会抛出一个 TimeoutExpired
异常。
- import subprocess
-
- try:
- # 运行命令,最多允许执行1秒
- subprocess.run(['sleep', '2'], timeout=1)
- except subprocess.TimeoutExpired:
- print('The subprocess did not finish within the timeout period!')
在这个例子中,我们尝试运行一个会睡眠2秒的子进程,但是设置了1秒的超时。因为子进程执行时间超过了超时设置,所以 TimeoutExpired
异常被抛出。