![](/img/trans.png)
[英]Python: subprocess.Popen().communicate() prints output of an SSH command to stdout instead of returning the output
[英]Python's Popen + communicate only returning the first line of stdout
我正在尝试使用我的命令行 git 客户端和 Python 的 I/O 重定向,以便在许多 git 存储库上自动执行一些常见操作。 (是的,这是 hack-ish。我以后可能会回去使用 Python 库来做这件事,但现在它似乎工作正常 :))
我希望能够捕获调用 git 的输出。 隐藏输出会更好看,捕获它会让我记录它,以防它有用。
我的问题是,当我运行 'git clone' 命令时,我无法获得超过第一行的输出。 奇怪的是,带有 'git status' 的相同代码似乎工作得很好。
我在 Windows 7 上运行 Python 2.7,我正在使用 cmd.exe 命令解释器。
我的侦探到目前为止:
当我用“git clone”调用 subprocess.call() 时,它运行良好,我在控制台上看到了输出(这证实了 git 正在生成输出,即使我没有捕获它)。 这段代码:
dir = "E:\\\\Work\\\\etc\\\\etc" os.chdir(dir) git_cmd = "git clone git@192.168.56.101:Mike_VonP/bit142_assign_2.git" #print "SUBPROCESS.CALL" + "="*20 #ret = subprocess.call(git_cmd.split(), shell=True)
将在控制台上产生此输出:
SUBPROCESS.CALL==================== Cloning into 'bit142_assign_2'... remote: Counting objects: 9, done. remote: Compressing objects: 100% (4/4), done. remote: Total 9 (delta 0), reused 0 (delta 0) Receiving objects: 100% (9/9), done. Checking connectivity... done.
如果我直接用 POpen 做同样的事情,我会在控制台上看到相同的输出(也没有被捕获)。 这段代码:
# (the dir = , os.chdir, and git_cmd= lines are still executed here) print "SUBPROCESS.POPEN" + "="*20 p=subprocess.Popen(git_cmd.split(), shell=True) p.wait()
将产生这个(实际上相同的)输出:
SUBPROCESS.POPEN==================== Cloning into 'bit142_assign_2'... remote: Counting objects: 9, done. remote: Compressing objects: 100% (4/4), done. remote: Total 9 (delta 0), reused 0 (delta 0) Receiving objects: 100% (9/9), done. Checking connectivity... done.
(显然我要在两次运行之间删除克隆的 repo,否则我会收到“一切都是最新的”消息)
如果我使用communication() 方法,我期望得到一个字符串,其中包含我在上面看到的所有输出。 相反,我只看到Cloning into 'bit142_assign_2'...
。
这段代码:
print "SUBPROCESS.POPEN, COMMUNICATE" + "="*20 p=subprocess.Popen(git_cmd.split(), shell=True,\\ bufsize = 1,\\ stderr=subprocess.PIPE,\\ stdout=subprocess.PIPE) tuple = p.communicate() p.wait() print "StdOut:\\n" + tuple[0] print "StdErr:\\n" + tuple[1]
将产生这个输出:
SUBPROCESS.POPEN, COMMUNICATE==================== StdOut: StdErr: Cloning into 'bit142_assign_2'...
一方面,我已经重定向了输出(正如您可以从它不在输出中看到的那样),但我也只捕获了第一行。
我已经尝试了很多很多东西(调用check_output
而不是 popen,使用带有check_output
的管道,使用带有check_output
管道,以及我可能忘记的其他东西)但没有任何效果 - 我只先捕获它输出线。
有趣的是,完全相同的代码确实可以与 'git status' 一起正常工作。 一旦 repo 被克隆,调用 git status 会产生三行输出(统称为“一切都是最新的”),第三个示例(POpen+communicate 代码)确实捕获了所有三行输出。
如果有人对我做错了什么有任何想法,或者对我可以尝试以更好地诊断这个问题的任何想法有任何想法,我将不胜感激。
尝试将--progress
选项添加到您的 git 命令。 这会强制 git 将进度状态发送到 stderr,即使 git 进程未连接到终端 - 通过subprocess
进程函数运行 git 时就是这种情况。
git_cmd = "git clone --progress git@192.168.56.101:Mike_VonP/bit142_assign_2.git"
print "SUBPROCESS.POPEN, COMMUNICATE" + "="*20
p = subprocess.Popen(git_cmd.split(), stderr=subprocess.PIPE, stdout=subprocess.PIPE)
tuple = p.communicate()
p.wait()
print "StdOut:\n" + tuple[0]
print "StdErr:\n" + tuple[1]
注意我无法在 Windows 上测试这个,但它在 Linux 上是有效的。
此外,没有必要指定shell=True
这可能是一个安全问题,因此最好避免。
这里有两个有趣的部分,一个是特定于 Python 的,另一个是特定于 Git 的。
使用subprocess
模块时,您可以选择控制您运行的程序的最多三个 I/O 通道:stdin、stdout 和 stderr。 这是真正的subprocess.call
和subprocess.check_call
以及subprocess.Popen
,但两者call
和check_call
马上致电新工艺对象的wait
方法,所以由于种种原因,这是不明智的供应subprocess.PIPE
对于stdout和/或带有这两个操作的 stderr。 1
除此之外,使用subprocess.call
等同于使用subprocess.Popen
。 其实call
的代码是一行代码:
def call(*popenargs, **kwargs):
return Popen(*popenargs, **kwargs).wait()
如果您选择不重定向任何 I/O 通道,则读取输入的程序会从 Python 会从同一个位置获取它,将输出写入 stdout 的程序会将其写入您自己的 Python 代码会的相同位置, 2以及写入的程序输出到 stderr 将它写到 Python 会写到的地方。
当然,您可以将 stdout 和/或 stderr 重定向到实际文件以及subprocess.PIPE
。 文件和管道不是交互式“终端”或“tty”设备(即,不被视为直接连接到人类)。 这将我们引向 Git。
Git 程序通常可以从 stdin 读取和/或写入 stdout 和/或 stderr。 Git 还可以调用其他程序,这些程序可以执行相同的操作,或者可以绕过这些标准 I/O 通道。
特别是,正如您所观察到的, git clone
主要写入其 stderr。 此外,正如mhawke 回答的那样,您必须添加--progress
以使 Git 将进度消息写入 stderr Git 不与交互式 tty 设备交谈。
如果 Git 在通过https
或ssh
克隆时需要密码或其他身份验证,Git 将运行一个辅助程序来获取它。 大多数情况下,这些程序完全绕过标准输入(通过在 POSIX 系统上打开/dev/tty
,或在 Windows 上打开等价物),以便与用户交互。 在您的自动化环境中这将如何工作,或者它是否会工作是一个很好的问题(但同样超出了本答案的范围)。 但这确实让我们回到了 Python,因为......
除了subprocess
模块,还有一些外部库, sh
和pexpect
,并内置到Python本身的一些设施通过pty
模块,可以打开一个伪终端:一个互动的tty设备,而不是直接连接到一个人的,已连接到您的程序。
使用 ptys 时,您可以让 Git 的行为与直接与人交谈时的行为相同——事实上,今天的“与人交谈”实际上是使用 ptys(或等效物)完成的,因为有程序运行各种窗口系统. 此外,要求人类输入密码的程序现在可以3与您自己的 Python 代码进行交互。 这可能是好的也可能是坏的(甚至两者都有),所以请考虑您是否希望这种情况发生。
1具体来说, communicate
方法的重点是管理最多三个流之间的 I/O 流量,如果它们中的任何一个或全部是PIPE
,而没有子进程楔子。 想象一下,如果您愿意,一个子进程将 64K 的文本打印到 stdout,然后将 64K 的文本打印到 stderr,然后再将 64K 的文本打印到 stdout,然后从 stdin 读取。 如果您尝试以任何特定顺序读取或写入其中任何一个,子流程将“卡住”等待您清除其他内容,而您将卡住等待子流程完成您选择先完成的任何一个。 communicate
作用是使用线程或特定于操作系统的非阻塞 I/O 方法来提供子进程输入,同时读取其 stdout 和 stderr,所有这些都是同时进行的。
换句话说,它处理了多路复用。 因此,如果您没有为三个 I/O 通道中的至少两个提供subprocess.PIPE
,绕过communicate
方法是安全的。 如果是,则不是(除非您实现自己的多路复用)。
这里有一个有点奇怪的边缘情况:如果为 stderr 输出提供subprocess.STDOUT
,这会告诉 Python 将子进程的两个输出定向到单个通信通道中。 这仅计为一个管道,因此如果您将子进程的 stdout 和 stderr 组合在一起,并且不提供输入,则可以绕过communicate
方法。
2事实上,子进程继承了进程的标准输入、标准输出和标准错误,如果你覆盖了它们,它们可能与 Python 的sys.stdin
、 sys.stdout
和sys.stderr
不匹配。 这进入细节可能最好在这里忽略。 :-)
3我说“可能”而不是“将”,因为/dev/tty
访问控制终端,并不是所有的 pty 都是控制终端。 这也变得复杂且特定于操作系统,并且也超出了本答案的范围。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.