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

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

在以下代码中:

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

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

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

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

I am calling different processes with the subprocess module. However, I have a question.

In the following codes:

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

and

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

Both work. After reading the docs, I came to know that shell=True means executing the code through the shell. So that means in absence, the process is directly started.

So what should I prefer for my case – I need to run a process and get its output. What benefit do I have from calling it from within the shell or outside of it.


回答 0

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

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

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

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

总之,使用shell=False

The benefit of not calling via the shell is that you are not invoking a ‘mystery program.’ On POSIX, the environment variable SHELL controls which binary is invoked as the “shell.” On Windows, there is no bourne shell descendent, only cmd.exe.

So invoking the shell invokes a program of the user’s choosing and is platform-dependent. Generally speaking, avoid invocations via the shell.

Invoking via the shell does allow you to expand environment variables and file globs according to the shell’s usual mechanism. On POSIX systems, the shell expands file globs to a list of files. On Windows, a file glob (e.g., “*.*”) is not expanded by the shell, anyway (but environment variables on a command line are expanded by cmd.exe).

If you think you want environment variable expansions and file globs, research the ILS attacks of 1992-ish on network services which performed subprogram invocations via the shell. Examples include the various sendmail backdoors involving ILS.

In summary, use shell=False.


回答 1

>>> 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被视为简单命令。

来源:子流程模块

>>> 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

Setting the shell argument to a true value causes subprocess to spawn an intermediate shell process, and tell it to run the command. In other words, using an intermediate shell means that variables, glob patterns, and other special shell features in the command string are processed before the command is run. Here, in the example, $HOME was processed before the echo command. Actually, this is the case of command with shell expansion while the command ls -l considered as a simple command.

source: Subprocess Module


回答 2

此处显示了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()

An example where things could go wrong with Shell=True is shown here

>>> 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...

Check the doc here: subprocess.call()


回答 3

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

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

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

Executing programs through the shell means that all user input passed to the program is interpreted according to the syntax and semantic rules of the invoked shell. At best, this only causes inconvenience to the user, because the user has to obey these rules. For instance, paths containing special shell characters like quotation marks or blanks must be escaped. At worst, it causes security leaks, because the user can execute arbitrary programs.

shell=True is sometimes convenient to make use of specific shell features like word splitting or parameter expansion. However, if such a feature is required, make use of other modules are given to you (e.g. os.path.expandvars() for parameter expansion or shlex for word splitting). This means more work, but avoids other problems.

In short: Avoid shell=True by all means.


回答 4

这里的其他答案充分说明了安全警告,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。

The other answers here adequately explain the security caveats which are also mentioned in the subprocess documentation. But in addition to that, the overhead of starting a shell to start the program you want to run is often unnecessary and definitely silly for situations where you don’t actually use any of the shell’s functionality. Moreover, the additional hidden complexity should scare you, especially if you are not very familiar with the shell or the services it provides.

Where the interactions with the shell are nontrivial, you now require the reader and maintainer of the Python script (which may or may not be your future self) to understand both Python and shell script. Remember the Python motto “explicit is better than implicit”; even when the Python code is going to be somewhat more complex than the equivalent (and often very terse) shell script, you might be better off removing the shell and replacing the functionality with native Python constructs. Minimizing the work done in an external process and keeping control within your own code as far as possible is often a good idea simply because it improves visibility and reduces the risks of — wanted or unwanted — side effects.

Wildcard expansion, variable interpolation, and redirection are all simple to replace with native Python constructs. A complex shell pipeline where parts or all cannot be reasonably rewritten in Python would be the one situation where perhaps you could consider using the shell. You should still make sure you understand the performance and security implications.

In the trivial case, to avoid shell=True, simply replace

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

with

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

Notice how the first argument is a list of strings to pass to execvp(), and how quoting strings and backslash-escaping shell metacharacters is generally not necessary (or useful, or correct). Maybe see also When to wrap quotes around a shell variable?

If you don’t want to figure this out yourself, the shlex.split() function can do this for you. It’s part of the Python standard library, but of course, if your shell command string is static, you can just run it once, during development, and paste the result into your script.

As an aside, you very often want to avoid Popen if one of the simpler wrappers in the subprocess package does what you want. If you have a recent enough Python, you should probably use subprocess.run.

  • With check=True it will fail if the command you ran failed.
  • With stdout=subprocess.PIPE it will capture the command’s output.
  • With text=True (or somewhat obscurely, with the synonym universal_newlines=True) it will decode output into a proper Unicode string (it’s just bytes in the system encoding otherwise, on Python 3).

If not, for many tasks, you want check_output to obtain the output from a command, whilst checking that it succeeded, or check_call if there is no output to collect.

I’ll close with a quote from David Korn: “It’s easier to write a portable shell than a portable shell script.” Even subprocess.run('echo "$HOME"', shell=True) is not portable to Windows.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。