Python's Popen + communicate 只返回标准输出的第一行
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 上 运行ning 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 做同样的事情,我会在
控制台(也 not 被捕获)。此代码:
# (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.
(显然我要删除 运行 之间的克隆回购,否则我会
收到 'Everything is up to date' 消息)
如果我使用 communicate() 方法,我希望得到一个字符串
包含我在上面看到的所有输出。相反,我只
请参阅行 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,使用 subprocess.call 的管道,使用 subprocess.popen 的管道,可能还有其他我已经尝试过的东西忘记了)但没有任何效果 - 我只捕获了第一行输出。
有趣的是,完全相同的代码 可以与 'git status' 一起正常工作。一旦 repo 被克隆,调用 git status 会产生三行输出(统称为 'everything is up to date'),第三个示例(POpen+communicate 代码)确实捕获了所有三行输出。
如果有人对我做错了什么有任何想法,或者对我可以尝试的任何事情有任何想法,以便更好地诊断这个问题,我将不胜感激。
尝试将 --progress
选项添加到您的 git 命令。这会强制 git 将进度状态发送到 stderr,即使 git 进程未附加到终端 - 当 运行 git 通过 subprocess
函数。
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]
N.B。我无法在 Windows 上测试它,但它在 Linux.
上有效
此外,没有必要指定 shell=True
,这可能是一个安全问题,因此最好避免。
这里有两部分值得关注,一部分是 Python-特定的,另一部分是 Git-特定的。
Python
使用 subprocess
模块时,您可以选择控制您 运行 程序的最多三个 I/O 通道:stdin、stdout 和 stderr。对于 subprocess.call
和 subprocess.check_call
以及 subprocess.Popen
都是如此,但是 call
和 check_call
都会立即调用新进程对象的 wait
方法,因此出于各种原因,为标准输出 and/or stderr 提供 subprocess.PIPE
这两个操作是不明智的。1
除此之外,使用subprocess.call
等同于使用subprocess.Popen
。事实上,call
的代码是一行代码:
def call(*popenargs, **kwargs):
return Popen(*popenargs, **kwargs).wait()
如果您选择不重定向任何 I/O 通道,读取输入的程序会从同一位置获取输入 Python,将输出写入标准输出的程序会将其写入同一位置您自己的 Python 代码会 2 并且将输出写入 stderr 的程序会将其写入相同的位置 Python 会。
当然,您可以将 stdout and/or stderr 重定向到实际文件,以及 subprocess.PIPE
s。文件和管道 不是 交互式 "terminal" 或 "tty" 设备(即,不被视为直接连接到人类)。这导致我们 Git.
Git
Git 程序通常可以从 stdin 读取 and/or 写入 stdout and/or stderr。 Git 也可能会调用其他程序,这些程序可能会执行相同的操作,或者可能会绕过这些标准 I/O 频道。
特别是,git clone
主要写入其标准错误,如您所见。此外,由于 ,您必须添加 --progress
才能使 Git 将进度消息写入标准错误 Git 未与交互式 tty 设备通信。
如果 Git 在通过 https
或 ssh
克隆时需要密码或其他身份验证,Git 将 运行 一个辅助程序来获取它。这些程序,在大多数情况下,完全绕过 stdin(通过在 POSIX 系统上打开 /dev/tty
,或在 Windows 上打开等价物),以便与用户进行交互。这在您的自动化环境中效果如何,或者它是否会起作用,这是一个很好的问题(但同样超出了本答案的范围)。但这确实让我们回到了 Python,因为 ...
Python
除了 subprocess
模块,还有一些外部库,sh
and pexpect
, and some facilities built into Python itself via the pty
module,可以打开一个伪 tty:一个交互式 tty 设备,它不是直接连接到人,而是已连接到您的程序。
当使用 ptys 时,您可以让 Git 的行为与直接与人交谈时的行为相同——事实上,"talking to a human" 今天实际上已经用 ptys(或等同物)完成了,因为有一些程序 运行 用于各种窗口系统。此外,要求人类输入密码的程序现在可以 3 与您自己的 Python 代码进行交互。这可能是好事也可能是坏事(甚至两者兼而有之),因此请考虑您是否希望这种情况发生。
1具体来说,communicate
方法的要点是管理最多三个流之间的 I/O 流量,如果有或全部其中 PIPE
,没有子进程楔形。想象一下,如果您愿意,一个子进程将 64K 的文本打印到 stdout,然后将 64K 的文本打印到 stderr,然后将另外 64K 的文本打印到 stdout,然后从 stdin 读取。如果您尝试以任何特定顺序读取或写入其中任何一个,子进程将 "get stuck" 等待您清除其他内容,而您将卡住等待子进程完成您选择先完成的任何一个. communicate
所做的是使用线程或 OS 特定的非阻塞 I/O 方法来提供子进程输入 而 读取其标准输出和标准错误, 所有同时。
换句话说,它处理了多路复用。因此,如果您不为三个 I/O 通道中的至少 两个 提供 subprocess.PIPE
,则绕过 communicate
方法是安全的。如果您是,则不是(除非您实现自己的多路复用)。
这里有一个有点奇怪的边缘情况:如果您为 stderr 输出提供 subprocess.STDOUT
,这会告诉 Python 将子进程的两个输出定向到一个通信通道。这算作只有一个管道,因此如果您组合子进程的标准输出和标准错误,并且不提供任何输入,则可以绕过 communicate
方法。
2其实子进程继承了进程的stdin、stdout、stderr,可能不匹配Python 的 sys.stdin
、sys.stdout
和 sys.stderr
(如果您覆盖了它们)。这进入细节可能最好在这里忽略。 :-)
3我说"may"而不是"will"因为/dev/tty
访问控制终端 ,并且并非所有 pty 都是控制终端。这也变得复杂且 OS-specific 并且也超出了这个答案的范围。
我正在尝试使用我的命令行 git 客户端和 Python 的 I/O 重定向来自动执行许多 git 上的一些常见操作] 回购。 (是的,这是 hack-ish。稍后我可能会返回并使用 Python 库来执行此操作,但目前看来效果还不错 :))
我希望能够捕获调用 git 的输出。隐藏输出会更好看,捕获它会让我记录它以备不时之需。
我的问题是,当我 运行 一个 'git clone' 命令 时,我只能得到第一行输出。奇怪的是,使用 'git status' 的相同代码似乎工作得很好。
我在 Windows 7 上 运行ning 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 做同样的事情,我会在 控制台(也 not 被捕获)。此代码:
# (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.
(显然我要删除 运行 之间的克隆回购,否则我会 收到 'Everything is up to date' 消息)
如果我使用 communicate() 方法,我希望得到一个字符串 包含我在上面看到的所有输出。相反,我只 请参阅行
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,使用 subprocess.call 的管道,使用 subprocess.popen 的管道,可能还有其他我已经尝试过的东西忘记了)但没有任何效果 - 我只捕获了第一行输出。
有趣的是,完全相同的代码 可以与 'git status' 一起正常工作。一旦 repo 被克隆,调用 git status 会产生三行输出(统称为 'everything is up to date'),第三个示例(POpen+communicate 代码)确实捕获了所有三行输出。
如果有人对我做错了什么有任何想法,或者对我可以尝试的任何事情有任何想法,以便更好地诊断这个问题,我将不胜感激。
尝试将 --progress
选项添加到您的 git 命令。这会强制 git 将进度状态发送到 stderr,即使 git 进程未附加到终端 - 当 运行 git 通过 subprocess
函数。
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]
N.B。我无法在 Windows 上测试它,但它在 Linux.
上有效此外,没有必要指定 shell=True
,这可能是一个安全问题,因此最好避免。
这里有两部分值得关注,一部分是 Python-特定的,另一部分是 Git-特定的。
Python
使用 subprocess
模块时,您可以选择控制您 运行 程序的最多三个 I/O 通道:stdin、stdout 和 stderr。对于 subprocess.call
和 subprocess.check_call
以及 subprocess.Popen
都是如此,但是 call
和 check_call
都会立即调用新进程对象的 wait
方法,因此出于各种原因,为标准输出 and/or stderr 提供 subprocess.PIPE
这两个操作是不明智的。1
除此之外,使用subprocess.call
等同于使用subprocess.Popen
。事实上,call
的代码是一行代码:
def call(*popenargs, **kwargs):
return Popen(*popenargs, **kwargs).wait()
如果您选择不重定向任何 I/O 通道,读取输入的程序会从同一位置获取输入 Python,将输出写入标准输出的程序会将其写入同一位置您自己的 Python 代码会 2 并且将输出写入 stderr 的程序会将其写入相同的位置 Python 会。
当然,您可以将 stdout and/or stderr 重定向到实际文件,以及 subprocess.PIPE
s。文件和管道 不是 交互式 "terminal" 或 "tty" 设备(即,不被视为直接连接到人类)。这导致我们 Git.
Git
Git 程序通常可以从 stdin 读取 and/or 写入 stdout and/or stderr。 Git 也可能会调用其他程序,这些程序可能会执行相同的操作,或者可能会绕过这些标准 I/O 频道。
特别是,git clone
主要写入其标准错误,如您所见。此外,由于 --progress
才能使 Git 将进度消息写入标准错误 Git 未与交互式 tty 设备通信。
如果 Git 在通过 https
或 ssh
克隆时需要密码或其他身份验证,Git 将 运行 一个辅助程序来获取它。这些程序,在大多数情况下,完全绕过 stdin(通过在 POSIX 系统上打开 /dev/tty
,或在 Windows 上打开等价物),以便与用户进行交互。这在您的自动化环境中效果如何,或者它是否会起作用,这是一个很好的问题(但同样超出了本答案的范围)。但这确实让我们回到了 Python,因为 ...
Python
除了 subprocess
模块,还有一些外部库,sh
and pexpect
, and some facilities built into Python itself via the pty
module,可以打开一个伪 tty:一个交互式 tty 设备,它不是直接连接到人,而是已连接到您的程序。
当使用 ptys 时,您可以让 Git 的行为与直接与人交谈时的行为相同——事实上,"talking to a human" 今天实际上已经用 ptys(或等同物)完成了,因为有一些程序 运行 用于各种窗口系统。此外,要求人类输入密码的程序现在可以 3 与您自己的 Python 代码进行交互。这可能是好事也可能是坏事(甚至两者兼而有之),因此请考虑您是否希望这种情况发生。
1具体来说,communicate
方法的要点是管理最多三个流之间的 I/O 流量,如果有或全部其中 PIPE
,没有子进程楔形。想象一下,如果您愿意,一个子进程将 64K 的文本打印到 stdout,然后将 64K 的文本打印到 stderr,然后将另外 64K 的文本打印到 stdout,然后从 stdin 读取。如果您尝试以任何特定顺序读取或写入其中任何一个,子进程将 "get stuck" 等待您清除其他内容,而您将卡住等待子进程完成您选择先完成的任何一个. communicate
所做的是使用线程或 OS 特定的非阻塞 I/O 方法来提供子进程输入 而 读取其标准输出和标准错误, 所有同时。
换句话说,它处理了多路复用。因此,如果您不为三个 I/O 通道中的至少 两个 提供 subprocess.PIPE
,则绕过 communicate
方法是安全的。如果您是,则不是(除非您实现自己的多路复用)。
这里有一个有点奇怪的边缘情况:如果您为 stderr 输出提供 subprocess.STDOUT
,这会告诉 Python 将子进程的两个输出定向到一个通信通道。这算作只有一个管道,因此如果您组合子进程的标准输出和标准错误,并且不提供任何输入,则可以绕过 communicate
方法。
2其实子进程继承了进程的stdin、stdout、stderr,可能不匹配Python 的 sys.stdin
、sys.stdout
和 sys.stderr
(如果您覆盖了它们)。这进入细节可能最好在这里忽略。 :-)
3我说"may"而不是"will"因为/dev/tty
访问控制终端 ,并且并非所有 pty 都是控制终端。这也变得复杂且 OS-specific 并且也超出了这个答案的范围。