子进程中'shell = True'的实际含义

时间:2010-07-03 18:39:55

标签: python subprocess

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

在以下代码中:

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

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

两者都有效。阅读文档后,我发现shell=True表示通过shell执行代码。这意味着在缺席的情况下,该过程将直接启动。

那么我应该更喜欢我的情况 - 我需要运行一个进程并获得它的输出。从shell内部或外部调用它有什么好处。

6 个答案:

答案 0 :(得分:145)

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

因此,调用shell会调用用户选择的程序,并且与平台有关。一般来说,避免通过shell进行调用。

通过shell调用允许您根据shell的常用机制扩展环境变量和文件globs。在POSIX系统上,shell将文件globs扩展为文件列表。在Windows上,shell不会扩展文件glob(例如“*。*”)(但命令行上的环境变量由cmd.exe扩展)。

如果您认为您需要环境变量扩展和文件全局,请研究1992年的ILS攻击对通过shell执行子程序调用的网络服务的问题。示例包括涉及sendmail的各种ILS后门。

总之,请使用shell=False

答案 1 :(得分:78)

>>> 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进程,并告诉它运行该命令。换句话说,使用中间shell意味着在命令运行之前处理命令字符串中的变量,glob模式和其他特殊shell特性。这里,在示例中,$ HOME在echo命令之前被处理。实际上,这是带有shell扩展的命令的情况,而命令ls -l被认为是一个简单的命令。

来源:Subprocess Module

答案 2 :(得分:35)

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

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

简而言之:一定要避免使用shell=True

答案 3 :(得分:31)

这里显示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()

答案 4 :(得分:14)

此处的其他答案充分解释了subprocess文档中也提到的安全警告。但除此之外,启动shell以启动您想要运行的程序的开销通常是不必要的,对于您实际上不使用任何shell功能的情况来说,这绝对是愚蠢的。此外,如果你不熟悉shell或它提供的服务,那么额外的隐藏复杂性应该会吓到你,尤其是

通配符扩展,变量插值和重定向都很容易用本机Python结构替换。一个复杂的shell管道,部分或全部不能在Python中合理地重写(专用外部工具,也许是封闭的源?)将是你可以考虑使用shell的一种情况。你应该对它感到难过。

在简单的情况下,只需替换

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

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

注意第一个参数是如何传递给execvp()的字符串列表,以及如何引用字符串和反斜杠转义的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。

答案 5 :(得分:0)

让我们假设您正在使用shell = False并将命令作为列表提供。一些恶意用户尝试注入“ rm”命令。 您会看到,“ rm”将被解释为一个参数,有效地“ ls”将尝试查找名为“ rm”的文件

>>> subprocess.run(['ls','-ld','/home','rm','/etc/passwd'])
ls: rm: No such file or directory
-rw-r--r--    1 root     root          1172 May 28  2020 /etc/passwd
drwxr-xr-x    2 root     root          4096 May 29  2020 /home
CompletedProcess(args=['ls', '-ld', '/home', 'rm', '/etc/passwd'], returncode=1)
如果无法正确控制输入,默认情况下

shell = False不安全。您仍然可以执行危险的命令。

>>> subprocess.run(['rm','-rf','/home'])
CompletedProcess(args=['rm', '-rf', '/home'], returncode=0)
>>> subprocess.run(['ls','-ld','/home'])
ls: /home: No such file or directory
CompletedProcess(args=['ls', '-ld', '/home'], returncode=1)
>>>

我正在容器环境中编写大多数应用程序,我知道正在调用哪个shell,并且我不接受任何用户输入。

因此,在我的用例中,我认为没有安全风险。创建长长的命令字符串要容易得多。希望我没有错。