我试图通过移植一个 Python 脚本来做正确的事情,该脚本通过以下方式调用多个 shell 命令行
subprocess.call(... | ... | ... , shell=True)
通过使用Popen
来避免shell=True
的安全风险。所以我写了一个小示例脚本来尝试一下。它执行命令行
awk '{print $1 " - " $2}' < scores.txt | sort | python uppercase.py > teams.txt
如下:
with open('teams.txt', 'w') as destination:
with open('scores.txt', 'r') as source:
p3 = Popen(['python', 'uppercase.py'], stdin=PIPE, stdout=destination)
p2 = Popen(['sort'], stdin=PIPE, stdout=p3.stdin)
p1 = Popen(['awk', '{print $1 " - " $2}'], stdin=source, stdout=p2.stdin)
p1.communicate()
该程序适用于小型数据集。
现在我对 communicate 文档中的以下行感到震惊方法:
Note The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
什么?但我有大量文件需要进行 awk'd 和排序等。我首先尝试使用 communicate
的原因是我看到了 subprocess.call
的警告:
Note Do not use stdout=PIPE or stderr=PIPE with this function as that can deadlock based on the child process output volume. Use Popen with the communicate() method when you need pipes.
我真的很困惑。看来我的选择是:
- 将
call
与shell=True
一起使用(他们说存在安全风险) - 将
PIPE
与call
结合使用(但存在死锁风险) - 使用
Popen
和通信
(但我的数据太大,有数百兆字节)。
我错过了什么?如何在没有 shell=True
的情况下在 Python 中为非常大的文件创建多个进程管道,或者 shell=True
可以接受吗?
最佳答案
只有当您使用诸如 stdout=PIPE
之类的内容时,有关“在内存中缓冲”的注释才有意义。它不适用于 stdout=file
(os.dup2()
在操作系统文件描述符级别进行重定向,对于 .communicate( 没有任何作用) )
方法)。
不要使用call
和PIPE
。 call()
(简化)只是 Popen().wait()
即,它不从管道读取。不要使用 PIPE 除非您从管道中读取(写入)(没有意义)。
在您的代码中p1.communicate()
不会读取任何数据。您可以将其替换为 p1.wait()
。您的代码缺少 p3.stdin.close(); ...; p2.stdin.close(); ...; p3.wait(), p2.wait()
否则,该代码适用于大文件。
在shell=True
如果命令是硬编码的(如您的问题所示),则不存在安全风险。如果该命令可能来自不受信任的源,那么如何运行该命令并不重要(在这种情况下,不受信任的源可能会运行它喜欢的任何命令)。如果只有一些参数来自不受信任的来源,那么您可以使用plumbum模块来避免自己重新实现管道:
from plumbum.cmd import awk, sort, python
(awk['{print $1 " - " $2}'] < untrusted_filename | sort |
python["uppercase.py"] > "teams.txt")()
参见How do I use subprocess.Popen to connect multiple processes by pipes?
关于Python 通信 vs shell=True,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25777777/