Python communicate 与 shell=True 的区别

2 投票
1 回答
934 浏览
提问于 2025-04-21 00:20

我正在尝试把一个Python脚本移植到另一个版本,这个脚本通过

subprocess.call(... | ... | ... , shell=True) 

调用了很多命令行。我的目标是避免使用shell=True带来的安全风险,所以我决定使用Popen。为此,我写了一个小示例脚本来测试一下。这个脚本执行的命令行是

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方法的这一行让我很困惑:

注意:读取的数据会缓存在内存中,所以如果数据量很大或没有限制,请不要使用这个方法。

什么?我有很多需要用awk处理和排序的大文件啊。其实我最开始想用communicate的原因是因为我看到subprocess.call的警告:

注意:不要在这个函数中使用stdout=PIPE或stderr=PIPE,因为这可能会导致死锁。需要使用管道时,请使用Popen和communicate()方法。

我真的很困惑。看起来我面临的选择有:

  • 使用callshell=True(他们说这有安全风险)
  • 使用PIPEcall(但这可能导致死锁)
  • 使用Popencommunicate(但我的数据太大,几百兆字节)。

我到底漏掉了什么?如何在Python中处理非常大的文件而不使用shell=True,或者说shell=True是可以接受的吗?

1 个回答

4

关于“缓存在内存中”的说明,只有在你使用像 stdout=PIPE 这样的设置时才有意义。如果你用 stdout=file,那么 os.dup2() 是在操作系统的文件描述符层面进行重定向,这样的话 .communicate() 方法就没什么用处了。

不要使用 callPIPE。简单来说,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")()

查看 如何使用 subprocess.Popen 通过管道连接多个进程?

撰写回答