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 文档对使用 PIPE
为 subprocess.call()
进行了以下说明:
注意不要在此函数中使用
stdout=PIPE
或stderr=PIPE
。如果子进程生成足够的输出到管道以填满操作系统管道缓冲区,则子进程将阻塞,因为管道没有被读取。
Python 2.7 subprocess.call()
:
注意不要将
stdout=PIPE
或stderr=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()
之间有什么区别使PIPE
对subprocess.call()
的安全性降低?
更具体:为什么subprocess.call()
“死锁基于子进程输出量。”,而不是Popen()
?
【问题讨论】:
// ,后续问题:Python 2 和 Python 3 之间的这种差异有何变化? 你为什么总是用//
开始一切?
@chepner:嗯,按钮确实说“添加评论”.... :)
// , Meta 的后续问题:为什么很少有人对 SO 有幽默感?
# @NathanBasanese 也许我是 completely missing the point,但 Python 的注释符号是 #
,这与 C++、php 和 javascript 不同。
【参考方案1】:
call()
is just Popen().wait()
(± error handling).
您不应将stdout=PIPE
与call()
一起使用,因为它不会从管道中读取数据,因此子进程将在填满相应的操作系统管道缓冲区后立即挂起。这是一张显示数据在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】:
call
和 Popen
都提供了访问命令输出的方法:
Popen
,您可以使用communicate
或向stdout=...
参数提供文件描述符或文件对象。
使用call
,您唯一的选择是将文件描述符或文件对象传递给stdout=...
参数(您不能将communicate
与此参数一起使用)。
现在,stdout=PIPE
与call
一起使用时不安全的原因是call
在子进程完成之前不会返回,这意味着所有输出都必须驻留在内存中直到那一刻,并且如果输出量过多,则会填满操作系统管道的缓冲区。
您可以验证上述信息的参考如下:
-
根据this,
call
和Popen
的参数相同:
上面显示的参数只是最常见的参数,描述 下面在常用参数中(因此在 缩写签名)。完整的函数签名与 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()使用和注意事项