subprocess.call()如何使用shell = False?

时间:2017-05-15 23:22:40

标签: python linux bash shell subprocess

我正在使用Python的subprocess模块来调用某些Linux命令行函数。该文档将shell=True参数解释为

  

如果 shell True,则指定的命令将通过shell执行

有两个例子,从描述性的角度来看对我来说是相同的(即他们都调用了一些命令行命令),但其中一个使用shell=True而另一个不使用

>>> subprocess.call(["ls", "-l"])
0

>>> subprocess.call("exit 1", shell=True)
1

我的问题是:

  • shell=False相比,使用shell=True运行命令的做法是什么?
  • 我的印象是subprocess.callcheck_call以及check_output都必须通过shell执行参数。换句话说,它怎么可能通过shell执行参数?

获得一些例子也很有帮助:

  • 可以通过shell=True无法完成的事情 shell=False以及为什么无法完成这些工作。
  • 反之亦然(虽然似乎没有这样的例子)
  • 无论是shell=True还是False以及无关紧要的事情都无关紧要

2 个答案:

答案 0 :(得分:10)

UNIX程序通过以下三个调用或其衍生物/等价物相互启动:

  • fork() - 创建自己的新副本。
  • exec() - 用不同的程序替换自己(如果你是副本,请这样做!)。
  • wait() - 等待另一个进程完成(可选,如果没有在后台运行)。

因此,使用shell=False,您就是这样做的(如下面的Python语法伪代码 - 如果不是wait()的阻塞调用,则排除subprocess.call()

pid = fork()
if pid == 0: # we're the child process, not the parent
  execlp("ls", "ls", "-l", NUL);
else:
  retval = wait(pid) # we're the parent; wait for the child to exit & get its exit status

而使用shell=True,您可以这样做:

pid = fork()
if pid == 0:
  execlp("sh", "sh", "-c", "ls -l", NUL);
else:
  retval = wait(pid)

请注意,对于shell=False,我们执行的命令为ls,而对于shell=True,我们执行的命令为sh

也就是说:

subprocess.Popen(foo, shell=True)

与:

完全相同
subprocess.Popen(
  ["sh", "-c"] + ([foo] if isinstance(foo, basestring) else foo),
  shell=False)

也就是说,执行/bin/sh的副本,并指示/bin/sh的副本将字符串解析为参数列表并自行执行ls -l

那么,为什么你使用shell=True

  • 您正在调用内置的shell。

    例如,exit命令实际上是shell本身的一部分,而不是外部命令。也就是说,这是fairly small set of commands,它们很少在shell实例的上下文中有用,该实例仅在单个subprocess.call()调用期间存在。

  • 你有一些shell构造(即重定向)的代码,如果没有它就难以模拟。

    例如,如果您的命令是cat one two >three,则语法>three重定向:它不是cat的参数,而是指令运行命令stdout=open('three', 'w')时设置['cat', 'one', 'two']的shell。如果您不想自己处理重定向和管道,则需要使用shell来执行此操作。

    一个稍微棘手的案例是cat foo bar | baz。要在没有shell的情况下执行此操作,您需要自己启动管道的两端:p1 = Popen(['cat', 'foo', 'bar'], stdout=PIPE), p2=Popen(['baz'], stdin=p1.stdout)

  • 你不会对安全漏洞感到厌恶。

    ...好吧,那是位太强,但不是太多。使用shell=True很危险。你不能这样做:Popen('cat -- %s' % (filename,), shell=True)没有shell注入漏洞:如果你的代码是用含有filename的{​​{1}}调用的,那么你的日子就会非常糟糕。另一方面,$(rm -rf ~)对所有可能的文件名都是安全的:文件名纯粹是数据,不是由shell或其他任何东西解析为源代码。

    可以在shell中编写安全脚本,但是你需要注意它。请考虑以下事项:

    ['cat', '--', filename]

    该代码是安全的(同样安全 - 就像让用户读取他们想要的任何文件那样安全),因为它从你的脚本代码传递你的文件名 - 但是这是安全的,因为传递给shell的字符串是固定的并且是硬编码的,参数化的内容是外部变量(filenames = ['file1', 'file2'] # these can be user-provided subprocess.Popen(['cat -- "$@" | baz', '_'] + filenames, shell=True) 列表)。即便如此,它只是“安全”到了某一点 - 像Shellshock这样的bug会在shell初始化时触发它会对其产生影响。

答案 1 :(得分:2)

  

我的印象是subprocess.call和check_call以及check_output都必须通过shell执行参数。

不,子进程完全能够直接启动程序(通过操作系统调用)。它不需要shell

  

shell = False

无法用shell = True完成的事情

您可以将shell=False用于任何只使用某些指定参数运行某些可执行文件的命令。

如果您的命令使用shell功能,则必须使用shell=True。这包括管道,|或重定向,或包含与;&&||等结合的复合语句。

因此,可以使用shell=False作为grep string file之类的命令。但是,由于grep string file | xargs something需要|shell=True之类的命令会出现。

因为shell具有python程序员并不总是直观的强大功能,所以除非你真的需要shell功能,否则使用shell=False被认为是更好的做法。例如,管道并不是真正需要的,因为它们也可以使用子流程'PIPE功能完成。