在Python subprocess中使用反引号

3

我希望通过一个Python脚本来运行这个Git命令,并获取它的输出:

git diff --name-only mybranch `git merge-base mybranch develop`

这个命令的目的是查看自上次与develop合并以来,mybranch分支发生了哪些更改。

为了实现这一目的,我使用了subprocess.Popen命令:

output = subprocess.Popen(["git", "diff", "--name-only", "mybranch", "`git merge-base mybranch develop`"], stdout=subprocess.PIPE, shell=True)

然而,这并不起作用。变量output.communicate()[0]只是给我一个git使用的输出 -- 实际上告诉我输入的命令是错误的。
我看到类似的问题在这里,但它只告诉我要使用shell=True,这并没有解决我的问题。
我也尝试连续运行这两个命令,但是那给了我和之前一样的输出。虽然在这一步中,我可能漏掉了某些东西。
任何帮助或提示都将不胜感激。

你为什么要尝试使用shell=True并传递参数列表?另外,为什么git在一个单独的列表中? - Padraic Cunningham
链接问题中的答案说,使用反引号只能在shell=True的情况下起作用。它们以没有特定原因的方式分为两个列表,我将进行编辑以避免混淆。 - Jokab
5
你应该使用带有shell=True的字符串,或者移除shell=True并传递一个单独参数的列表。 - Padraic Cunningham
1
谢谢,这就是答案。我可能在文档中错过了这个。 - Jokab
1
请注意,在Bash中,反引号已经被弃用:$(cmd)语法不容易被误读,并且如果需要,您可以嵌套它。有关更多详细信息,请参见BashGuide中的BashFAQ/082“为什么$(...)比(backticks)更受欢迎?” 此外,在SO评论中编写反引号很麻烦。 :) - PM 2Ring
你可以考虑在Python中使用第二个subprocess.Popen()调用来调用git merge-base并收集其输出以供后续调用使用;这就是你的其他命令正在做的事情,只是告诉shell创建运行第二个调用的子进程。自己完成比让shell代劳更有效率。(如果这是一个Web服务,并且mybranch来自不受信任的来源,出于安全原因,你也应该这样做;Shell注入可不是闹着玩的)。 - Charles Duffy
2个回答

2

反引号和子进程

反引号是一个shell特性,你可能别无选择,只能使用shell=True,但需要传递一个shell命令字符串,而不是一系列参数的列表。

因此,在处理您特定的命令时(假设它首先能正常工作):

process = subprocess.Popen("git diff --name-only mybranch `git merge-base mybranch develop`", stdout=subprocess.PIPE, shell=True)

注意,当您调用Popen()时,会得到一个进程,我认为它不应该被称为output

下面是一个使用反引号的简单示例

>>> process = subprocess.Popen('echo `pwd`', stdout=subprocess.PIPE, shell=True)
>>> out, err = process.communicate()
>>> out
'/Users/bakkal\n'

或者您可以使用$(cmd)语法。
>>> process = subprocess.Popen('echo $(pwd)', stdout=subprocess.PIPE, shell=True)
>>> out, err = process.communicate()
>>> out
'/Users/bakkal\n'

以下是未能成功实现反引号的方法:

>>> process = subprocess.Popen(['echo', '`pwd`'], stdout=subprocess.PIPE, shell=True)
>>> out, err = process.communicate()
>>> out
'\n'
>>> process = subprocess.Popen(['echo', '`pwd`'], stdout=subprocess.PIPE, shell=False)
>>> out, err = process.communicate()
>>> out
'`pwd`\n'

@PM2Ring 添加了一个使用 $(cmd)subprocess 的示例。 - bakkal
谢谢。我决定将我的评论移到问题中,因为那里可能更合适。 - PM 2Ring
1
你有选择的权利,你可以删除 shell=True - jfs
你需要手动执行反引号扩展,因为根据我的测试,似乎无法在没有 shell=True 的情况下扩展反引号或 $(cmd),而反引号是问题的关键。 - bakkal

2
在POSIX系统上,参数列表传递给/bin/sh -c,也就是说,只有第一个参数被识别为shell命令,也就是说,shell在没有任何参数的情况下运行git,这就是为什么您会看到使用信息。如果要使用shell=True,则应将命令作为字符串传递。来自subprocess文档

On POSIX with shell=True, the shell defaults to /bin/sh. If args is a string, the string specifies the command to execute through the shell. This means that the string must be formatted exactly as it would be when typed at the shell prompt. This includes, for example, quoting or backslash escaping filenames with spaces in them. If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional arguments to the shell itself. That is to say, Popen does the equivalent of:

Popen(['/bin/sh', '-c', args[0], args[1], ...])
在这种情况下,您不需要使用shell=True
#!/usr/bin/env python
from subprocess import check_output

merge_base_output = check_output('git merge-base mybranch develop'.split(), 
                                 universal_newlines=True).strip()
diff_output = check_output('git diff --name-only mybranch'.split() +
                           [merge_base_output])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接