子过程中“ shell = True”的实际含义


260

我正在使用该subprocess模块调用不同的进程。但是,我有一个问题。

在以下代码中:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

callProcess = subprocess.Popen(['ls', '-l']) # without shell

两者都可以。阅读文档后,我知道这shell=True意味着通过外壳执行代码。因此,这意味着在不存在的情况下,该过程将直接启动。

因此,对于我的情况,我更喜欢什么-我需要运行一个流程并获取其输出。从外壳内部或外部调用它有什么好处。


21
第一个命令不正确:-l如果传递给/bin/sh(shell)而不是Unix上ls程序。在大多数情况下,应使用字符串参数而不是列表。shell=Trueshell=True
jfs

1
关于“该过程直接启动”:会吗?
allyourcode '16

9
声明“两者都起作用”。关于那两个电话的说法是不正确的并且具有误导性。呼叫的工作方式不同。仅从切换shell=TrueFalse反之亦然是一个错误。来自文档:“在带shell = True的POSIX上,(...)如果args是序列,则第一项指定命令字符串,任何其他项将被视为shell本身的其他参数。”。在Windows上有自动转换功能,这可能是不希望的。
mbdevpl

Answers:


183

不通过外壳调用的好处是您没有在调用“神秘程序”。在POSIX上,环境变量SHELL控制哪个二进制文件作为“外壳”被调用。在Windows上,没有bourne shell后代,只有cmd.exe。

因此,调用外壳程序将调用用户选择的程序,并且该程序与平台有关。一般来说,避免通过外壳调用。

通过shell调用确实允许您根据shell的通常机制扩展环境变量和文件glob。在POSIX系统上,外壳程序将文件全局扩展为文件列表。在Windows上,无论如何,shell都不会扩展文件glob(例如“ *。*”)(但是cmd.exe 扩展命令行上的环境变量)。

如果您认为需要环境变量扩展和文件文件,请研究ILS1992-ish对网络服务的攻击,这些攻击通过外壳执行子程序调用。例子包括sendmail涉及各种后门ILS

总之,使用shell=False


2
感谢你的回答。尽管我确实不在那个应该担心漏洞利用的阶段,但是我了解您正在做什么。
user225312

55
如果您一开始很粗心,那么后顾之忧将无济于事。;)
Heath Hunnicutt 2010年

如果要限制子进程的最大内存怎么办?stackoverflow.com/questions/3172470/…–
Pramod

8
关于的说法$SHELL不正确。引用subprocess.html:“在Unix上shell=True,shell默认为/bin/sh。” (不是$SHELL
marcin '16

1
@ user2428107:是的,如果您在Perl上使用反引号调用,则您正在使用Shell调用并打开相同的问题。open如果您想以安全的方式调用程序并捕获输出,请使用3+ arg 。
ShadowRanger

137
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

将shell参数设置为true值会导致子进程产生一个中间shell进程,并告诉它运行命令。换句话说,使用中间外壳程序意味着在运行命令之前先处理命令​​字符串中的变量,全局模式和其他特殊外壳程序功能。在此示例中,$ HOME是在echo命令之前处理的。实际上,这是带有shell扩展的命令的情况,而命令ls -l被视为简单命令。

来源:子流程模块


16
不知道为什么这不是所选答案。到目前为止,实际上与该问题匹配的人
Rodrigo Lopez Guerra

1
同意。这是我了解shell = True含义的好例子。
user389955

2
将shell参数设置为true值会导致子进程产生一个中间的shell进程,并告诉它运行命令 。为什么不接受此答案???为什么?
pouya

我认为问题是要调用的第一个参数是列表,而不是字符串,但是如果shell为False,则会出现错误。将命令更改为列表即可完成这项工作
林肯·兰德尔·麦克法兰

抱歉,我之前的评论未完成。需要明确的是:我经常看到子进程与shell = True一起使用,并且命令是一个字符串,例如'ls -l'(我希望避免此错误),但是子进程需要一个列表(和一个字符串作为一个元素列表) 。要在不调用shell的情况下运行(以及与此相关的安全性问题),请使用list subprocess.call(['ls','-l'])
Lincoln Randall McFarland,

42

此处显示了Shell = True可能出错的示例

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

在此处检查文档:subprocess.call()


6
该链接非常有用。正如链接所指出的那样:执行包含来自不受信任源的未经处理的输入的shell命令,会使程序容易受到shell注入的攻击,这是一个严重的安全漏洞,可能导致任意命令执行。因此,在从外部输入构造命令字符串的情况下,强烈建议不要使用shell = True。
jtuki

39

通过外壳执行程序意味着将根据调用的外壳的语法和语义规则来解释传递给程序的所有用户输入。充其量,这只会给用户带来不便,因为用户必须遵守这些规则。例如,必须转义包含特殊外壳字符(如引号或空格)的路径。最糟糕的是,这会导致安全漏洞,因为用户可以执行任意程序。

shell=True有时可以方便地利用特定的Shell功能(例如单词拆分或参数扩展)。但是,如果需要此功能,则可以利用其他模块(例如,os.path.expandvars()用于参数扩展或shlex字分割)。这意味着更多的工作,但避免了其他问题。

简而言之:务必避免shell=True


16

这里的其他答案充分说明了安全警告,subprocess文档中也提到了这些警告。但是除此之外,启动外壳程序以启动要运行的程序的开销通常是不必要的,对于您实际上没有使用任何外壳程序功能的情况而言,这绝对是愚蠢的。而且,额外的隐藏复杂性会让您感到恐惧,特别是如果您对外壳程序或它提供的服务不是很熟悉的话。

在与shell的交互非常简单的地方,您现在需要Python脚本的阅读者和维护者(可能是您将来的自己,也可能不是您将来的自己)来理解Python和shell脚本。记住Python的座右铭“明确胜于隐含”。即使Python代码比等效的(并且通常非常简洁)shell脚本要复杂一些,您最好还是删除外壳并用本机Python构造替换功能。尽量减少在外部过程中完成的工作并尽可能地将控制保持在自己的代码中通常是一个好主意,这仅仅是因为它可以提高可见性并减少副作用(有害的或有害的)的风险。

通配符扩展,变量插值和重定向都很容易用本机Python构造替换。复杂的Shell管道(其中的部分或全部无法用Python合理地重写)可能是您可以考虑使用Shell的一种情况。您仍然应该确保您了解性能和安全性含义。

在平凡的情况下,要避免shell=True,只需替换

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

请注意,第一个参数如何是要传递给的字符串列表execvp(),以及通常如何不需要引用字符串和使用反斜杠的shell元字符(或有用或正确)。也许还会看到何时将引号括在shell变量周围?

顺便说一句,您经常会想避免Popen包装中的一个较简单的包装subprocess程序满足您的要求。如果您的Python版本足够新,则应该使用subprocess.run

  • 随着check=True如果命令你运行失败就会失败。
  • 有了stdout=subprocess.PIPE这将捕获命令的输出。
  • 有点晦涩难懂,universal_newlines=True它将输出解码为正确的Unicode字符串(bytes否则,在Python 3中只是在系统编码中)。

如果不是这样,则对于许多任务,您希望check_output从命令获取输出,同时检查命令是否成功,或者check_call是否没有要收集的输出。

最后,我引用大卫·科恩(David Korn)的话说:“编写可移植的shell比移植可移植的shell脚本容易。” 甚至subprocess.run('echo "$HOME"', shell=True)不能移植到Windows。


我以为报价来自拉里·沃尔(Larry Wall),但Google告诉我否则。
点钟

这是个高谈阔论的话题,但是没有替代的技术建议:在OS-X上,我试图获取通过“ open”启动的Mac App的pid:process = subprocess.Popen('/ usr / bin / pgrep- n'+ app_name,shell = False,stdout = subprocess.PIPE,stderr = subprocess.PIPE)app_pid,err = process.communicate()---除非我使用shell = True,否则它不起作用。怎么办?
Motti Shneor '16

关于如何避免有shell=True很多问题,许多问题都有很好的答案。您碰巧选择了一个有关原因的文件
三人房

@MottiShneor感谢您的反馈;添加了简单的示例
Tripleee '16

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.