【问题标题】:Python Subprocess: Unable to Escape QuotesPython 子进程:无法转义引号
【发布时间】:2016-07-22 15:28:52
【问题描述】:

我知道以前有人问过类似的问题,但它们似乎都已通过重新设计参数的传递方式(即使用列表等)得到解决。

但是,我这里有个问题,我没有那个选项。有一个特定的命令行程序(我使用的是 Bash shell),我必须将一个带引号的字符串传递给它。它不能不加引号,不能有重复的参数,只能是单引号或双引号。

command -flag 'foo foo1'

我不能使用command -flag foo foo1,也不能使用command -flag foo -flag foo1。我认为这是对命令接收输入的编程方式的疏忽,但我无法控制它。

我传递的参数如下:

self.commands = [
                self.path,
                '-flag1', quoted_argument,
                '-flag2', 'test',
                ...etc...
                ]
process = subprocess.Popen(self.commands, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
results = process.communicate(input)

quoted_argument 类似于“foo foo1 foo2”。 我尝试转义单引号 ("\'foo foo1 foo2\'"),但没有得到任何输出。

我知道这被认为是不好的做法,因为它难以解释,但我没有其他选择。有什么想法吗?

【问题讨论】:

  • 代替quoted_argument,你能告诉我们你实际输入命令的字符串吗?令人费解的是,您的示例是 command -flag 'foo foo1'self.commands 有不同的标志。但是......只是做quoted_argument = "foo foo1"(即完全删除引号)应该可以工作。
  • 或者,只使用 shell Popen("command -flag 'foo foo1'", shell=True)
  • 我试图用命令概括,实际命令有点不同。我实际上正在获取一个列表并将其连接到引用的参数中,因此显示起来有点复杂。如果我不需要,我也不想连接 shell=True 的字符串。我会尝试删除引号。
  • @tdelaney 删除引号似乎可行,谢谢。您可以将其作为答案,我会接受,或者如果您愿意,我可以自己做。

标签: python bash shell subprocess quotes


【解决方案1】:

shell 将命令字符串分解为列表。引号告诉外壳程序将多个单词放入单个列表项中。由于您是自己构建列表,因此您将单词添加为不带引号的单个项目。

这两个Popen 命令是等价的

Popen("command -flag 'foo foo1'", shell=True)
Popen(["command", "-flag", "foo foo1"])

编辑

这个答案处理 shell 中的转义字符。如果您不使用 shell,则无需添加任何引号或转义符,只需放入字符串本身即可。跳过 shell 还有其他问题,例如管道命令、运行后台作业、使用 shell 变量等。这些都可以在 python 中而不是 shell 中完成。

【讨论】:

  • 注意:第一个Popen() 调用运行一个中间shell 进程。第二个在启动command之前不运行shell
  • 添加外壳可能是一个重大变化。见Actual meaning of shell=True in subprocess
  • @tripleee - shell=True 有几个问题,很高兴指出这一点。但是这个问题是专门关于在参数中使用引号而不是一般的shell。
  • 当然;不过,说两者等价并不完全正确。这是一个很好的规范答案的候选者,因为这个话题出现了很多,但许多问题都不清楚或没有重点。但是,如果能恰当地涵盖这种细微差别,那就太好了。
  • @tripleee - 随时发布完整的答案。
【解决方案2】:

我发现非常有用的进程和 shell 的心智模型:

多年来,这种心智模式对我帮助很大。

操作系统中的进程接收代表参数的字符串数组。在 Python 中,可以从sys.argv 访问此数组。在 C 中,这是传递给 main 函数的 argv 数组。以此类推。

当您打开终端时,您正在该终端内运行一个 shell,例如 bashzsh。如果你运行这样的命令会发生什么?

$ /usr/bin/touch one two

发生的情况是,shell 解释了您编写的命令并将其用空格分割以创建数组 ["/usr/bin/touch", "one", "two"]。然后它使用该参数列表启动一个新进程,在本例中创建两个名为 onetwo 的文件。

如果您想要一个名为one two 的文件并带有空格怎么办?你不能像你想要的那样向 shell 传递参数列表,你只能传递一个字符串。像 Bash 和 Zsh 这样的 shell 使用单引号来解决这个问题:

$ /usr/bin/touch 'one two'

shell 将使用参数["/usr/bin/touch", "one two"] 创建一个新进程,在这种情况下创建一个名为one two 的文件。

贝壳具有管道等特殊功能。使用 shell,您可以执行以下操作:

$ /usr/bin/echo 'This is an example' | /usr/bin/tr a-z A-Z
THIS IS AN EXAMPLE

在这种情况下,shell 以不同的方式解释 | 字符。 In 创建一个带有参数["/usr/bin/echo", "This is an example"] 的进程和另一个带有参数["/usr/bin/tr", "a-z", "A-Z"] 的进程,并将前者的输出通过管道传递给后者的输入。

这如何应用于 Python 中的 subprocess

现在,在 Python 中,您可以将 subprocessshell=False 一起使用(这是默认值,或者与 shell=True 一起使用。如果您使用默认行为 shell=False,那么 subprocess 期望您将其传递给参数列表。您不能使用特殊的外壳功能,例如外壳管道。从好的方面来说,您不必担心转义外壳的特殊字符:

import subprocess
# create a file named "one two"
subprocess.call(["/usr/bin/touch", "one two"])

如果您确实想使用 shell 功能,您可以执行以下操作:

subprocess.call(
    "/usr/bin/echo 'This is an example' | /usr/bin/tr a-z A-Z",
    shell=True,
)

如果您使用没有特别保证的变量,请记住对命令进行转义:

import shlex
import subprocess

subprocess.call(
    "/usr/bin/echo " + shlex.quote(variable) + " | /usr/bin/tr a-z A-Z",
    shell=True,
)

(请注意,shlex.quote 仅适用于 UNIX shell,不适用于 Windows 上的 DOS。)

【讨论】:

  • 请注意,我在回答中主要谈论的是类 UNIX 系统。答案在 Windows 中应该是类似的,但是关于如何在 Windows 上启动进程的细节是不同的,并且 Windows 默认不附带 Bash 或 Zsh。
猜你喜欢
  • 2021-02-26
  • 2017-02-07
  • 2022-08-03
  • 1970-01-01
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多