使用shell=True时subprocess.call()参数被忽略,且使用列表

9 投票
2 回答
3372 浏览
提问于 2025-04-28 01:48

我正在尝试让Python的subprocess.call方法通过一个列表来接受一些命令参数,这个列表由一系列字符串组成,这是Python文档中提到的。为了在把它放进我的实际脚本之前先了解这个行为,我打开了IPython,运行了一些涉及不同shell设置和参数命令的组合,得到了以下结果:

In [3]: subprocess.call(['ls', '-%sl' %'a'])
total 320
drwxr-xr-x  20 Kohaugustine  staff   680 Oct 15 16:55 .
drwxr-xr-x   5 Kohaugustine  staff   170 Sep 12 17:16 ..
-rwxr-xr-x   1 Kohaugustine  staff  8544 Oct 15 16:55 a.out
-rwxr-xr-x   1 Kohaugustine  staff  8544 Oct  3 10:28 ex1-6
-rw-r--r--@  1 Kohaugustine  staff   204 Oct  3 10:28 ex1-6.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Oct  3 10:15 ex1-7
-rw-r--r--@  1 Kohaugustine  staff    71 Oct  3 10:15 ex1-7.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:22 hello
-rw-r--r--@  1 Kohaugustine  staff    58 Sep 12 16:27 hello.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:24 hello.o
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:24 hello_1.o
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:27 hello_2.o
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:27 hello_3.o
-rwxr-xr-x   1 Kohaugustine  staff  8544 Oct 15 16:55 lesson_1-5
-rw-r--r--@  1 Kohaugustine  staff   185 Sep 28 10:35 lesson_1-5.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 21 10:06 temperature.o
-rw-r--r--@  1 Kohaugustine  staff   406 Sep 21 09:54 temperature_ex1-3.c
-rw-r--r--@  1 Kohaugustine  staff   582 Sep 21 10:06 temperature_ex1-4.c
-rw-r--r--@  1 Kohaugustine  staff   178 Sep 23 17:21 temperature_ex1-5.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 23 17:21 temperature_ex1-5.o
Out[3]: 0

In [4]: subprocess.call(['ls', '-%sl' %'a'], shell=True)
a.out           ex1-7           hello.c         hello_2.o       lesson_1-5.c            temperature_ex1-4.c
ex1-6           ex1-7.c         hello.o         hello_3.o       temperature.o           temperature_ex1-5.c
ex1-6.c         hello           hello_1.o       lesson_1-5      temperature_ex1-3.c     temperature_ex1-5.o
Out[4]: 0

In [6]: subprocess.call(['ls', '-al'])    
total 320
drwxr-xr-x  20 Kohaugustine  staff   680 Oct 15 16:55 .
drwxr-xr-x   5 Kohaugustine  staff   170 Sep 12 17:16 ..
-rwxr-xr-x   1 Kohaugustine  staff  8544 Oct 15 16:55 a.out
-rwxr-xr-x   1 Kohaugustine  staff  8544 Oct  3 10:28 ex1-6
-rw-r--r--@  1 Kohaugustine  staff   204 Oct  3 10:28 ex1-6.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Oct  3 10:15 ex1-7
-rw-r--r--@  1 Kohaugustine  staff    71 Oct  3 10:15 ex1-7.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:22 hello
-rw-r--r--@  1 Kohaugustine  staff    58 Sep 12 16:27 hello.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:24 hello.o
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:24 hello_1.o
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:27 hello_2.o
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 12 16:27 hello_3.o
-rwxr-xr-x   1 Kohaugustine  staff  8544 Oct 15 16:55 lesson_1-5
-rw-r--r--@  1 Kohaugustine  staff   185 Sep 28 10:35 lesson_1-5.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 21 10:06 temperature.o
-rw-r--r--@  1 Kohaugustine  staff   406 Sep 21 09:54 temperature_ex1-3.c
-rw-r--r--@  1 Kohaugustine  staff   582 Sep 21 10:06 temperature_ex1-4.c
-rw-r--r--@  1 Kohaugustine  staff   178 Sep 23 17:21 temperature_ex1-5.c
-rwxr-xr-x   1 Kohaugustine  staff  8496 Sep 23 17:21 temperature_ex1-5.o
Out[6]: 0

In [7]: subprocess.call(['ls', '-al'], shell = True)
a.out           ex1-7           hello.c         hello_2.o       lesson_1-5.c            temperature_ex1-4.c
ex1-6           ex1-7.c         hello.o         hello_3.o       temperature.o           temperature_ex1-5.c
ex1-6.c         hello           hello_1.o       lesson_1-5      temperature_ex1-3.c     temperature_ex1-5.o
Out[7]: 0

看起来每当设置shell=True时,输出似乎和以下内容是一样的:

In [9]: subprocess.call(['ls'])
a.out           ex1-7           hello.c         hello_2.o       lesson_1-5.c            temperature_ex1-4.c
ex1-6           ex1-7.c         hello.o         hello_3.o       temperature.o           temperature_ex1-5.c
ex1-6.c         hello           hello_1.o       lesson_1-5      temperature_ex1-3.c     temperature_ex1-5.o
Out[9]: 0

我很困惑;当我设置shell=True时,'-a'选项怎么了?难道shell没有读取它吗?我读过文档,上面说当shell=True时,我指定的命令会通过shell执行,这应该意味着ls -a被传递给了shell并由它处理。那么为什么在[4]和[7]中会出现这样的行为呢?另外,文档没有直接解释这一点(虽然它确实提到当我们设置shell=False时,subprocess不会做什么);那么当我们设置shell=False时,这意味着什么呢?是不是在操作系统中生成了一个新进程,而这个新进程并没有让shell来控制它?

另外,可能会觉得我在[3]和[4]中使用格式字符串很奇怪,这是因为在我实际使用subprocess.call的脚本中,我需要依赖这些格式字符串来替换合适的命令选项。我不能把某些命令行选项硬编码进去。使用纯字符串作为参数也不行,因为在我的脚本中会有一个方法需要对这些命令进行列表操作。我不知道是否有更好的方法来处理这个问题,所以如果有人能提供不同的建议,那将非常有帮助。

非常感谢!

暂无标签

2 个回答

1

当你在使用 shell=True 并且传入一个列表时,额外的参数是传给 shell 本身 的,而不是传给在 shell 中运行的命令。这些参数可以在 shell 脚本中通过 argv[0] 来引用,表示为 $0, $1 等等。

最简单的建议就是“别这么做”:如果你想传一个列表,就不要使用 shell=True; 如果你想传一个字符串,记得总是使用 shell=True


不过,确实可以通过特定的方式来构造你的命令,以便读取这些参数。下面是一个违反我上面规则的例子——这是一个你无法在不使用 shell=True 的情况下实现的命令[*1](并且需要 executable='/bin/bash',以避免依赖于你的操作系统使用 bash 来执行 /bin/sh),因为它依赖于 bash 内置的 printf 版本(支持 %q 作为扩展):

subprocess.call([
    "printf '%q\\n' \"$0\" \"$@\"",
    'these strings are\r\n',
    '"shell escaped" in the output from this command',
    "so that the output can *safely* be run through eval",
    "observe that no /tmp/owned file is created",
    "including when the output of this script is run by bash as code:"
    "$(touch /tmp/owned) \"$(touch /tmp/owned)\"",
    '$(touch /tmp/owned) \'$(touch /tmp/owned)\'',
], shell=True, executable='/bin/bash')

[*1] - 如果忽略使用 /bin/bash 作为 argv[0] 并且 shell=False 的情况。

15

shell 设置为 True 时,第一个参数会被加到 ["/bin/sh", "-c"] 这个列表后面。如果这个参数是一个列表,最终的列表会是:

["/bin/sh", "-c", "ls", "-al"]

也就是说,传给 -c 选项的参数是 ls,而不是 ls -al。这里的 -al 是传给 shell 本身的第一个参数,而不是传给 ls 的。

在使用 shell=True 时,通常你只需要传一个字符串,让 shell 按照它正常的规则来分割这个字符串。

# Produces ["/bin/sh", "-c", "ls -al"]
subprocess.call("ls -al", shell=True)

在你的情况下,似乎根本不需要使用 shell=True

撰写回答