Python的Popen +只返回stdout的第一行

时间:2016-09-19 02:12:03

标签: python git popen communicate

我试图使用我的命令行git客户端和Python的I / O重定向,以便在很多git repos上自动执行一些常见操作。 (是的,这是hack-ish。我可能会回去使用Python库来执行此操作,但是现在看起来似乎没问题:))

我希望能够捕获调用git的输出。隐藏输出会看起来更好,捕获它会让我记录它以防它有用。

我的问题是,当我运行一个' git clone'时,我不能获得超过第一行的输出。命令即可。奇怪的是,相同的代码具有' git status'似乎工作得很好。

我在Windows 7上运行Python 2.7,并且我使用cmd.exe命令解释器。

到目前为止我的调查:

  1. 当我使用" 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.
    
  2. 如果我直接用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.
    

    (显然我在运行之间删除了克隆的回购,否则我就是这样 得到一切都是最新的'消息)

  3. 如果我使用的是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'...
    

    一方面,我重定向了输出(正如你从中可以看到的那样 它不在输出中)但我也只捕获第一行。

  4. 我尝试过很多东西(调用check_output而不是popen,使用带有subprocess.call的管道,使用带有subprocess.popen的管道,以及我可能忘记的其他东西)但没有任何作用 - 我只捕获第一行输出。

    有趣的是,完全相同的代码 正确使用' git status' 。一旦克隆了repo,调用git status会产生三行输出(统称为“所有内容都是最新的”),第三个例子(POpen +通信代码)确实捕获了所有三行输出。

    如果有人对我做错了什么有任何想法,或者我可以尝试任何想法,以便更好地诊断这个问题,我将非常感激。

2 个答案:

答案 0 :(得分:1)

尝试在您的git命令中添加--progress选项。即使git进程未连接到终端,这也会强制git向stderr发出进度状态 - 这是通过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]

N.B。我无法在Windows上测试它,但它在Linux上有效。

此外,没有必要指定shell=True,这可能是一个安全问题,因此最好避免使用。

答案 1 :(得分:1)

这里有两个感兴趣的部分,一个是特定于Python的,另一个是特定于Git的。

的Python

使用subprocess模块时,您可以选择控制运行的程序的最多三个I / O通道:stdin,stdout和stderr。对于subprocess.callsubprocess.check_call以及subprocess.Popen都是如此,但callcheck_call都会立即调用新进程对象的wait方法,因此,出于各种原因,使用这两个操作为stdout和/或stderr提供subprocess.PIPE是不明智的。 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中

Git程序通常可以从stdin读取和/或写入stdout和/或stderr。 Git也可以调用其他程序,这些程序也可以这样做,或者可以绕过这些标准的I / O通道。

特别是,正如您所观察到的,git clone主要写入其stderr。此外,作为mhawke answered,您必须添加--progress以使Git向stderr写入进度消息Git不与交互式tty设备通信。

如果Git在通过httpsssh克隆时需要密码或其他身份验证,Git将运行一个辅助程序来实现此目的。这些程序在很大程度上完全绕过 stdin(通过在POSIX系统上打开/dev/tty,或在Windows上等效),以便与用户交互。在您的自动化环境中,这将如何运作,或者它是否会起作用是一个很好的问题(但同样超出了这个答案的范围)。但这确实将我们带回了Python,因为......

的Python

subprocess模块外,还有一些外部库,shpexpect,以及Python本身via the pty module内置的一些工具,可以打开伪tty :交互式tty设备,它不是直接连接到人,而是连接到您的程序。

当使用ptys时,你可以让Git的行为与直接与人交谈时的行为相同 - 事实上,“今天与人交谈”实际上是用ptys(或同等的)来完成的,因为有些程序在运行各种窗口系统。此外,要求人们输入密码的程序可能 3 现在与您自己的Python代码进行交互。这可能是好的也可能是坏的(甚至两者都有),所以请考虑是否要这样做。

1 具体来说,communicate方法的要点是管理最多三个流之间的I / O流量,如果它们中的任何一个或全部是{{1}没有子进程楔。想象一下,如果你愿意的话,一个子程序将64K的文本输出到stdout,然后将64K的文本输出到stderr,然后另一个64K的文本输出到stdout,然后从stdin读取。如果您尝试以任何特定的顺序读取或写入任何这些,则子进程将“卡住”等待您清除其他内容,而您将无法等待子进程完成您选择首先完成的任何一个。而PIPE所做的是使用线程或特定于操作系统的非阻塞I / O方法来提供子进程输入,同时读取它的stdout和stderr,所有这些都是同时进行的。

换句话说,它处理多路复用。因此,如果您没有为三个I / O通道中的至少两个提供communicate,则绕过subprocess.PIPE方法是安全的。如果 ,则不是(除非您实现自己的多路复用)。

这里有一个有点奇怪的边缘情况:如果你为stderr输出提供communicate,这告诉Python将子进程的两个输出指向一个通信通道。这仅计为一个管道,因此如果将子进程的stdout和stderr组合在一起,并且不提供任何输入,则可以绕过subprocess.STDOUT方法。

2 实际上,子进程继承了进程的stdin,stdout和stderr,它们可能与Python的communicate不匹配,{{1} }和sys.stdin如果你已经超越了那些。这可能是最好的忽略细节。 : - )

3 我说“可能”代替“将”,因为sys.stdout访问控制终端,并非所有ptys都是控制终端。这也很复杂,特定于操作系统,也超出了这个答案的范围。