子进程

什么是子进程

当前正在执行的程序叫做主进程,比如你运行 python xxx.py

任何进程(包括“主进程”自己)要跑外部工具(git、ffmpeg、ls等等外部程序),都必须启动一个“子进程”来跑它,python内置了开启子进程方法 subprocess

如获取当前目录信息,在linux上是终端执行 ls -l,而如下代码则是代替你执行这个操作。

import subprocess

cmd = ["ls", "-l"]  # win下是["cmd", "/c", "dir"]
subprocess.run(cmd)

再比如调用新的进程执行python,甚至其它程序。

subprocess.run(["python", "other_script.py", "--arg", "123"])
subprocess.run(["jre/bin/java", "-jar", "test.jar"])

参数

capture_output

捕获原始输出(默认情况下执行ls -l 的输出会直接打印到“当前进程的终端/控制台”上,这个就是原始输出),捕获之后原始将不再输出,进而被抓到返回值里去了

import subprocess

cmd = ["ls", "-l"]
result = subprocess.run(
    cmd,
    capture_output=True,  # 捕获输出
    text=True             # 捕获输出内容->解码成字符串
)
print(result.stdout)

执行结果

stdout与stderr

调用 subprocess.run() 时,会返回一个 CompletedProcess 对象(表示该子进程已经执行完成的结果快照),其中常用属性有:

  • result.stdout:子进程的标准输出(standard output)
  • result.stderr:子进程的标准错误输出(standard error)
提示

约定俗成的用法(程序员一般会这么做):

  • 正常结果一般写到 stdout(如对应自己编程中 console.log)
  • 错误信息、警告等一般写到 stderr (如对应自己编程中 console.error)

但这只是惯例,不强制,成功时也可能有 stderr 输出,失败时也可能往 stdout 输出。

因为操作系统只负责给进程准备三个管道:stdin标准输入、stdout标准输出、stderr标准错误,
它不决定写什么内容,也不决定“啥是成功/失败”。 系统只是把进程写入 stdout/stderr 的内容送到对应的地方(终端、管道、被 Python/Node 读取等)。

  • C 程序里printf() 默认写到 stdout,fprintf(stderr, "err") 写到 stderr
  • Python 里:print() 默认写到 sys.stdout,print(..., file=sys.stderr) 写到 stderr
  • Node 里是你/库调用 console.log / console.error
  • Java 里:System.out.println() 写到 stdout,System.err.println() 写到 stderr
  • 各种命令行工具(git、ls、curl、npm、node 自己)内部也会写 stdout/stderr

returncode

res.returncode子进程的退出码(exit code),用来表示这个命令/程序最后是怎么结束的,通常 0 表示成功,非 0 表示失败,以 Python 举例:

import subprocess

cmd=["ls", "not_exist"]
res = subprocess.run(cmd, capture_output=True, text=True)
print(res.returncode)  # 操作结果(成功/失败/其它)
print(res.stdout)      # 成功信息
print(res.stderr)      # 错误信息
提示

谁设的这个数字?同样也有约定俗成,在子进程里由程序自己决定的。

  • C:return 0; / return 1; / exit(1);
  • Python:sys.exit(0) / sys.exit(1)
  • Node:process.exit(0) / process.exit(1)

父进程只是把这个数字取出来放到 result.returncode,方便判断命令是否“成功”:

  • “成功/失败”主要看 returncode
  • 输出内容是什么,主要看 stdout / stderr 写了什么

来个实际例子

单元测中 利用断言,来让子进程收集错误信息

other_script.py
assert 1==2
run_test.py
cmd=["py", "./other_script.py"]
res = subprocess.run(cmd, capture_output=True, text=True)
if res.stdout:
    print("STDOUT:", res.stdout)
if res.stderr:
    print("STDERR:", res.stderr)
print(f"pytest 返回码: {res.returncode}")

最终控制台输出如下

PS python run-test.py
STDERR: Traceback (most recent call last):
  File "other_script.py", line 1, in <module>
    assert 1==2
           ^^^^
AssertionError

pytest 返回码: 1