subprocess.call() 和 subprocess.Popen() 之间有啥区别使 PIPE 对前者的安全性降低?

Posted

技术标签:

【中文标题】subprocess.call() 和 subprocess.Popen() 之间有啥区别使 PIPE 对前者的安全性降低?【英文标题】:What difference between subprocess.call() and subprocess.Popen() makes PIPE less secure for the former?subprocess.call() 和 subprocess.Popen() 之间有什么区别使 PIPE 对前者的安全性降低? 【发布时间】:2015-11-28 15:38:58 【问题描述】:

我已经查看了他们两个的文档。

J.F. 在这里的评论提示了这个问题:Retrieving the output of subprocess.call()

subprocess.call() 的当前 Python 文档对使用 PIPEsubprocess.call() 进行了以下说明:

注意不要在此函数中使用stdout=PIPEstderr=PIPE。如果子进程生成足够的输出到管道以填满操作系统管道缓冲区,则子进程将阻塞,因为管道没有被读取。

Python 2.7 subprocess.call()

注意不要将stdout=PIPEstderr=PIPE 与此功能一起使用,因为这可能会根据子进程输出量而死锁。当你需要管道时,使用 Popen 和communicate() 方法。

Python 2.6 不包含此类警告。

另外,subprocess.call() and subprocess.check_call() 似乎没有办法访问他们的输出,除了使用 stdout=PIPE 和通信():

https://docs.python.org/2.6/library/subprocess.html#convenience-functions

注意,如果要向进程的stdin发送数据,则需要使用stdin=PIPE创建Popen对象。同样,要在结果元组中获得 None 以外的任何内容,您也需要提供 stdout=PIPE 和/或 stderr=PIPE

https://docs.python.org/2.6/library/subprocess.html#subprocess.Popen.communicate

subprocess.call()subprocess.Popen() 之间有什么区别使 PIPEsubprocess.call() 的安全性降低?

更具体:为什么subprocess.call()“死锁基于子进程输出量。”,而不是Popen()

【问题讨论】:

// ,后续问题:Python 2 和 Python 3 之间的这种差异有何变化? 你为什么总是以//开头? @chepner:嗯,按钮确实说“添加评论”.... :) // , Meta 的后续问题:为什么很少有人对 SO 有幽默感? # @NathanBasanese 也许我是 completely missing the point,但 Python 的注释符号是 #,这与 C++、phpjavascript 不同。 【参考方案1】:

call() is just Popen().wait() (± error handling).

您不应将stdout=PIPEcall() 一起使用,因为它不会从管道中读取数据,因此子进程将在填满相应的操作系统管道缓冲区后立即挂起。这是一张显示数据在command1 | command2 shell 管道中如何流动的图片:

不管你的 Python 版本是什么——管道缓冲区(看图片)在你的 Python 进程之外。 Python 3 不使用 C stdio,但它只影响内部缓冲。当内部缓冲区被刷新时,数据进入管道。如果command2(你的父Python程序)没有从管道中读取,那么command1(例如,由call()启动的子进程)将在管道缓冲区满时挂起(pipe_size = fcntl(p.stdout, F_GETPIPE_SZ) ~65K on my Linux 机器(最大值为/proc/sys/fs/pipe-max-size ~1M)。

如果您稍后从管道中读取,则可以使用stdout=PIPE,例如,使用Popen.communicate() 方法。你也可以read from process.stdout (the file object that represents the pipe) directly。

【讨论】:

// ,看起来“管道”比喻效果很好。我不知道我可以这么理解它,以至于可能有一个 stuffed 管道。让我想知道为什么该命令的内核管道缓冲区不只是自动刷新,或者为什么call() 不只是刷新该缓冲区。为了扩展“管道”的比喻,如果管道安装在某个可能无法从管道中出来的地方,为什么不将压力激活连接(电磁阀?)连接到排水槽? (“刷新缓冲区!让代码更强大!”-可能的口号?) // ,我可以猜到为什么有些人称系统程序员为“管道工”。这一切都在管道中。 有一种说法是“自动刷新管道缓冲区”;它被称为:“drop data”实现为stdout=DEVNULL(您可以安全地使用它与call())。 // 啊,不过以后可能就不能再用communicate()输出call()或者check_call()输出了吧? @NathanBasanese: call() 返回孩子的退出状态——它是一个普通整数(在 POSIX 上为 8 位范围,在 Windows 上为 32 位);你不能用它打电话给communicate()。无论如何,子进程已经死了,即使它的 PID 在call() 返回时可能已经被其他进程重用了。 check_call() 只是 call(),如果退出状态为非零,则会引发异常。仅使用 POSIX 调用来实现 shell 管道将是一个有用的练习:pipe、fork、exec、dup2、waitpid,例如 recursive-pipe.c【参考方案2】:

callPopen 都提供了访问命令输出的方法:

使用Popen,您可以使用communicate 或向stdout=... 参数提供文件描述符或文件对象。 使用call,您唯一的选择是将文件描述符或文件对象传递给stdout=... 参数(您不能将communicate 与此参数一起使用)。

现在,stdout=PIPEcall 一起使用时不安全的原因是call 在子进程完成之前不会返回,这意味着所有输出都必须驻留在内存中直到那一刻,并且如果输出量过多,则会填满操作系统管道的缓冲区。

您可以验证上述信息的参考如下:

    根据this,callPopen 的参数相同:

上面显示的参数只是最常见的参数,描述 下面在常用参数中(因此在 缩写签名)。完整的函数签名与 Popen 构造函数的那个​​ - 这个函数传递所有提供的 参数直接通过该接口。

    根据this,stdout 参数的可能值为:

有效值为 PIPE,一个现有的文件描述符(一个正 整数),现有文件对象和无。 PIPE 表示一个新的 应该创建到孩子的管道

【讨论】:

// 呃,第四段中的stdout=POPEN 是指stdout=PIPE,还是POPEN 是一个有价值的文件描述符? // 就像我说的,我确实通读了文档。我读了太多遍了。这让我很难过。另外,关于要点,我知道您必须传递文件描述符或文件对象,并且 PIPE 是一个坏主意,但我只是想知道 为什么 在`subprocess 方法的情况。另外,在第四段中,为什么第一部分出现在便利函数中,而不是出现在 Popen() 中?为什么这种差异会导致“这意味着......”部分? @NathanBasanese 嗨,关于stdout=POPEN 的事情,是的,这是一个错字。现在它被纠正了。关于您的其他疑问:原因是使用Popen() + PIPE,您可以多次调用communicate(),以避免内部操作系统管道的缓冲区被填充,而对于call() + PIPE,您不能调用communicate()一旦你调用call(),你的进程就会阻塞。

以上是关于subprocess.call() 和 subprocess.Popen() 之间有啥区别使 PIPE 对前者的安全性降低?的主要内容,如果未能解决你的问题,请参考以下文章

subprocess.call() 和 subprocess.Popen() 之间有啥区别使 PIPE 对前者的安全性降低?

subprocess.call() 和 subprocess.Popen() 之间有啥区别使 PIPE 对前者的安全性降低?

Python下的subprocess.call()使用和注意事项

subprocess.call 使用

Python执行外部命令(subprocess,call,Popen)

Python调用外部程序——os.system()和subprocess.call()