子流程优于 os.system

Advantages of subprocess over os.system

我最近遇到一些关于堆栈溢出的 post 说子进程比 os.system 好得多,但是我很难找到确切的优势。

我 运行 的一些例子: https://docs.python.org/3/library/os.html#os.system

"The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function."

虽然不知道它在哪些方面更强大,但我知道在很多方面使用子流程更容易,但它实际上在某些方面更强大吗?

另一个例子是:

The advantage of subprocess vs system is that it is more flexible (you can get the stdout, stderr, the "real" status code, better error handling, etc...).

这个 post 有 2600 多票。再次找不到关于更好的错误处理或真实状态代码的含义的任何详细说明。

post 的最高评论是:

Can't see why you'd use os.system even for quick/dirty/one-time. subprocess seems so much better.

同样,我明白它让一些事情变得更容易,但我很难理解为什么,例如:

subprocess.call("netsh interface set interface \"Wi-Fi\" enable", shell=True)

好多少
os.system("netsh interface set interface \"Wi-Fi\" enabled")

任何人都可以解释它更好的原因吗?

原因有很多,但在docstring中直接提到了主要原因:

>>> os.system.__doc__
'Execute the command in a subshell.'

对于几乎所有需要子进程的情况,不希望生成子 shell。这是不必要和浪费的,它增加了一层额外的复杂性,并引入了几个新的漏洞和故障模式。使用 subprocess 模块省去了中间人。

首先,你省去了中间商; subprocess.call 默认情况下避免生成检查您的命令的 shell,并直接生成请求的进程。这很重要,因为除了效率方面的问题之外,您对默认 shell 行为没有太多控制权,而且它实际上通常对转义不利。

特别是,不要这样做:

subprocess.call('netsh interface set interface "Wi-Fi" enable')

因为

If passing a single string, either shell must be True (see below) or else the string must simply name the program to be executed without specifying any arguments.

相反,您将执行:

subprocess.call(["netsh", "interface", "set", "interface", "Wi-Fi", "enable"])

注意这里所有逃脱的噩梦都消失了。 subprocess 处理转义(如果 OS 希望参数作为单个字符串 - 例如 Windows)或将分隔的参数直接传递给相关的系统调用(execvp 在 UNIX 上)。

将此与必须自己处理转义进行比较,尤其是在跨平台方式下(cmd 与 POSIX sh 的转义方式不同) ,尤其是中间的 shell 弄乱了你的东西(相信我,你不想知道在调用 cmd /k 时为你的命令提供 100% 安全转义是什么邪恶的混乱)。

此外,当使用中间没有 shell 的 subprocess 时,您肯定会得到 正确的 return 代码。如果启动进程失败,您会得到一个 Python 异常,如果您得到一个 return 代码,它实际上是启动程序的 return 代码。使用 os.system 你无法知道你获得的 return 代码是否来自启动的命令(如果 shell 设法启动它通常是默认行为)或者它是一些来自 shell 的错误(如果它未能成功启动)。


除了参数 splitting/escaping 和 return 代码之外,您还可以更好地控制启动过程。即使使用 subprocess.call(这是 subprocess 功能上最基本的实用函数),您也可以重定向 stdinstdoutstderr,可能与已启动的进程通信. check_call 类似,它避免了忽略失败退出代码的风险。 check_output 涵盖了 check_call 的常见用例 + 将所有程序输出捕获到字符串变量中。

一旦你通过了 call & friends(就像 os.system 一样阻塞),还有更强大的功能 - 特别是 Popen 对象允许你工作与启动的进程异步。你可以启动它,可能通过重定向的流与它交谈,在做其他事情的同时检查它是否不时 运行ning,等待它完成,向它发送信号并杀死它 - 所有这些东西除了 os.system 提供的单纯同步“默认 stdin/stdout/stderr 通过 shell 启动进程并等待它完成”之外的方法。


所以,总而言之,subprocess:

  • 即使是最基本的水平(call 和朋友),您:
    • 通过传递 Python 个参数列表来避免转义问题;
    • 避免 shell 干扰您的命令行;
    • 您有异常或您启动的进程的真实退出代码;没有混淆 program/shell 退出代码;
    • 有可能捕获标准输出并通常重定向标准流;
  • 当您使用 Popen 时:
    • 您不限于同步接口,但实际上您可以在子进程 运行;
    • 的同时做其他事情
    • 你可以控制子进程(检查它是否是运行ning,与之通信,杀死它)。

鉴于 subprocessos.system 能做的更多 - 并且以更安全、更灵活(如果你需要)的方式 - 没有理由使用 system相反。