subprocess
stdin stdout stderr
subprocess.run(command, check=True, text=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
与 subprocess.run(command, check=True, text=True)
区别在哪?
主要区别如下:
输入输出重定向
1
2
3
4
5
6
7
8
9
10
11
12
13# 默认方式(不指定)
# 继承父进程的标准输入、输出、错误流
subprocess.run(command, check=True, text=True)
# 指定 stdin/stdout/stderr
subprocess.run(
command,
check=True,
text=True,
stdin=subprocess.PIPE, # 重定向标准输入
stdout=subprocess.PIPE, # 重定向标准输出
stderr=subprocess.PIPE # 重定向标准错误
)主要不同点:
stdin=subprocess.PIPE
:子进程的标准输入被重定向到一个管道stdout=subprocess.PIPE
:子进程的标准输出被捕获到一个管道stderr=subprocess.PIPE
:子进程的标准错误输出被捕获到一个管道
使用场景:
1
2
3
4
5
6
7
8
9
10# 默认方式:命令直接输出到控制台
subprocess.run(["ls", "-l"])
# 捕获输出方式:将输出存储在变量中
result = subprocess.run(
["ls", "-l"],
capture_output=True,
text=True
)
print(result.stdout)详细对比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 不指定 PIPE 的情况
result1 = subprocess.run(["echo", "hello"], check=True, text=True)
# 输出会直接打印到控制台
# result1.stdout 为 None
# 指定 PIPE 的情况
result2 = subprocess.run(
["echo", "hello"],
check=True,
text=True,
stdout=subprocess.PIPE
)
# result2.stdout 会包含 "hello\n"
# 输出不会直接打印到控制台完整示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import subprocess
# 不捕获输出
subprocess.run(["ls", "-l"], check=True, text=True)
# 捕获并处理输出
result = subprocess.run(
["ls", "-l"],
check=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
print("标准输出:", result.stdout)
print("错误输出:", result.stderr)
关键区别:
- 默认方式:输出直接到控制台
- 使用 PIPE:
- 输出被捕获到内存
- 不直接显示在控制台
- 可以通过
result.stdout/stderr
获取输出 - 适合需要处理命令输出的场景
通常建议:
- 需要捕获或处理输出时,使用 PIPE
- 只是执行命令,不关心输出时,使用默认方式
subprocess.run and subprocess.Popen
subprocess
的作用:
- 允许 Python 程序调用和控制外部系统命令或其他程序
- 创建的是子进程,不是并行的 Python 进程
- 主要用于系统交互,执行外部命令
subprocess.run()
和 subprocess.Popen()
都是用于在 Python 中执行外部命令的方法,但它们有一些关键区别:
抽象层次:
subprocess.run()
是对subprocess.Popen()
的高级封装。我们可以通过查看 Python 的源码来验证这一点。让我们看看run()
函数的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs):
if input is not None:
if kwargs.get('stdin') is not None:
raise ValueError('stdin and input arguments may not both be used.')
kwargs['stdin'] = PIPE
if capture_output:
if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
raise ValueError('stdout and stderr arguments may not be used '
'with capture_output.')
kwargs['stdout'] = PIPE
kwargs['stderr'] = PIPE
with Popen(*popenargs, **kwargs) as process:
try:
stdout, stderr = process.communicate(input, timeout=timeout)
except TimeoutExpired as exc:
process.kill()
if _mswindows:
exc.stdout, exc.stderr = process.communicate()
else:
process.wait()
raise
except: # Including KeyboardInterrupt, communicate handled that.
process.kill()
# We don't call process.wait() as .__exit__ does that for us.
raise
retcode = process.poll()
if check and retcode:
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
return CompletedProcess(process.args, retcode, stdout, stderr)主要区别:
subprocess.Popen()
:- 创建进程后立即返回
- 需要手动管理进程(等待、读取输出、关闭)
- 提供更细粒度的进程控制
- 适合需要更复杂进程管理的场景
subprocess.run()
:- 创建进程并等待其完成
- 自动处理进程等待、输出捕获
- 返回一个
CompletedProcess
对象 - 更简单、更直接的使用方式
- 适合简单的命令执行
使用示例:
Popen()
方式:1
2
3
4
5
6
7
8
9import subprocess
# 手动管理进程
process = subprocess.Popen(['ls', '-l'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
stdout, stderr = process.communicate()
print(stdout)run()
方式:1
2
3
4
5
6
7import subprocess
# 简单直接的方式
result = subprocess.run(['ls', '-l'],
capture_output=True,
text=True)
print(result.stdout)实现原理:
从源码可以看出,
run()
实际上是:- 创建一个
Popen
对象 - 使用
communicate()
方法等待进程完成 - 处理超时
- 检查返回码
- 返回一个包含执行结果的
CompletedProcess
对象
- 创建一个
性能和使用建议:
- 对于简单的命令执行,使用
run()
- 对于需要实时交互、复杂进程控制的场景,使用
Popen()
- 对于简单的命令执行,使用
总结:subprocess.run()
是对 subprocess.Popen()
的高级封装,提供了更简单、更直接的命令执行方式,同时在底层仍然使用 Popen
来创建和管理进程。