子进程相对于 os.system 的优势

Posted

技术标签:

【中文标题】子进程相对于 os.system 的优势【英文标题】:Advantages of subprocess over os.system 【发布时间】:2017-11-27 14:16:16 【问题描述】:

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

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

“子流程模块提供了更强大的工具来生成新流程并检索其结果;使用该模块比使用此功能更可取。”

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

另一个例子是:

https://***.com/a/89243/3339122

subprocess vs system的优势在于它更灵活(你可以获得stdout、stderr、“真正的”状态码、更好的错误处理等...)。

这篇文章有 2600+ 票。再次找不到任何关于更好的错误处理或真实状态代码意味着什么的详细说明。

对该帖子的最高评论是:

不明白为什么你会使用 os.system 即使是快速/肮脏/一次性。 subprocess 似乎好多了。

再一次,我知道它使一些事情变得稍微容易一些,但我很难理解为什么,例如:

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

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

谁能解释一下它变得更好的一些原因吗?

【问题讨论】:

@JonathonReinhart 仅作记录。无论有没有 shell=True,它实际上都可以正常工作。我确实已经阅读了文档。这也不是我的代码为什么工作或不工作的问题。如果您愿意,我可以更新示例,问题仍然是它们之间的差异。在您得出结论或进行其他研究之前,请参阅:***.com/questions/3172470/…。我知道代码是如何工作的,但我只是不知道为什么要使用它而不是其他东西。 那一定是windows实现的一些奇怪的神器。它在一个操作系统上“工作”这一事实并不意味着它被正确使用。 Calling an external command in Python的可能重复 【参考方案1】:

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

特别是,不要这样做:

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

因为

如果传递单个字符串,shell 必须是 True(见下文),否则字符串必须简单地命名要执行的程序而不指定任何参数。

相反,你会这样做:

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

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

将此与必须自己处理转义进行比较,尤其是以跨平台方式(cmd 与 POSIX sh 的转义方式不同),尤其是中间的外壳会弄乱你的东西(相信我,在调用cmd /k 时,您不会想知道为您的命令提供 100% 安全转义是多么糟糕的事情。

另外,当使用subprocess 中间没有shell 时,您肯定会得到正确 的返回码。如果启动进程失败,你会得到一个 Python 异常,如果你得到一个返回码,它实际上是启动程序的返回码。使用os.system,您无法知道您获得的返回码是来自已启动的命令(如果外壳设法启动它,这通常是默认行为)还是来自外壳的一些错误(如果它没有设法启动它)。


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

一旦您通过call 和朋友(就像os.system 一样阻塞),就会有更强大的功能 - 特别是Popen 对象允许您异步处理启动的进程。您可以启动它,可能通过重定向的流与它交谈,检查它是否在做其他事情时不时运行,等待它完成,向它发送信号并杀死它——所有这些都是除了单纯的os.system 提供的同步“使用默认 stdin/stdout/stderr 通过 shell 启动进程并等待它完成”。


所以,总结一下,subprocess

即使在最基本的层面(call 和朋友),你: 通过传递 Python 参数列表来避免转义问题; 避免 shell 弄乱你的命令行; 您有异常或您启动的进程的真正退出代码;不会混淆程序/shell 退出代码; 可以捕获标准输出并在一般情况下重定向标准流; 当您使用Popen 时: 您不限于同步接口,但实际上您可以在子流程运行时执行其他操作; 您可以控制子进程(检查它是否正在运行、与之通信、杀死它)。

鉴于 subprocess 所做的事情远远超过 os.system 所能做的 - 并且以一种更安全、更灵活(如果需要的话)的方式 - 没有理由改用 system

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

谢谢,我会点赞评论。不能完全回答我在人们谈论的帖子中提到的更好的错误处理、真实状态代码或其他内容。甚至您的帖子也与他们的帖子一样,不确定您的意思是什么新的漏洞/故障模式,并且还不确定需要生成子shell 的情况。无法想象这小小的浪费会得到如此多的支持。 shell 允许您与操作系统交互。 Python 还具有与操作系统交互所需的所有功能。为什么要求 Python 要求 shell 与操作系统交互?额外的复杂性来自于必须将诸如管道和转义之类的东西翻译成 shell 语言。要了解这些漏洞,请搜索shell injection。

以上是关于子进程相对于 os.system 的优势的主要内容,如果未能解决你的问题,请参考以下文章

Python执行脚本方法

Supervisor 配置详解

pycharm下 os.system执行命令返回有中文乱码

subprocess以及常用的封装函数

[ python编程 ] subprocess模块学习总结

获取子进程的pid