如何在运行git命令时获取subprocess stdout?

时间:2014-09-23 03:47:04

标签: python git subprocess

我有一个用python编写的程序,并在其中使用了git命令.. 出于某种原因,我不想使用git-python或其他代替子进程。 但我目前仍然坚持获得git clone输出。

我尝试了一些代码段。有些可以使用ping 8.8.8.8之类的命令,但不适用git clone

例如

使用线程

def log_worker(stdout):
    while True:
        last = non_block_read(stdout).strip() 
        if last != "":
            print(last)


def non_block_read(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.read()
    except:
        return ''

def test():
    mysql_process = subprocess.Popen(
        "ping google.com",
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)

    thread = Thread(target=log_worker, args=[mysql_process.stdout])
    thread.daemon = True
    thread.start()

    mysql_process.wait()
    thread.join(timeout=1)

test()

newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            print('tt')
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            print('last', last)
            while last not in newlines:
                print("loop")
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
        shell = True
    )
    for line in unbuffered(proc):
        print('new line')
        print line

example()

和最常见的一个

for line in iter(proc.stdout.readline, ''):
    sys.stdout.write('{:.2f}  {}\n'.format(
        time.time() - start,
        line.rstrip()
    ))
    sys.stdout.flush()

所有这些都适用于ping google.com,但不适用于git clone。 有什么方法可以解决这个问题吗? 提前谢谢!

UPDATE1: 从表面上看,我只是希望获得git clone的完成百分比。不需要日志或任何日志文件。

2 个答案:

答案 0 :(得分:4)

当没有写入终端时,git clone没有任何输出到stdout或stderr,除非出错。

当写入终端时,当然它有很多输出 - 但输出是不断覆盖的进度条。通常情况下,你不会想要它 - 它会成为一大堆控制角色和重复的线条。

但如果你想要它,有两种选择。


首先,您可以使用PTY(Pseudo-TTY)。您可以使用os.openpty创建一个PTY,然后将PTY显式关闭到子进程。或者你可以使用os.forkpty来处理分叉并自动连接PTY所以你所要做的就是调用os.exec函数之一。或者您可以使用pty模块。 (并不完全清楚哪个更便携; openptyforkpty声称pty更便于携带,而且从概念上讲它是这样设计的......但它&# 39; s也只在Linux上测试过。)

请注意git想要将PTY作为其stderr,而不是它的标准输出。


或者,大多数git命令都有一个--progress标志,即使它不是终端,也会导致它们向stderr写入进度。至少在记录here的版本中,这包括clone,但您当然应该检查本地版本的man。所以,可能就是你所需要的。 (另请参阅--verbose标志。)

但是,这可能不太好。对我来说,当我提供一个没有附加termcaps的PTY时,我会得到每一行,然后是\r而不是\n来覆盖它;当我使用--progress选项时,git检测到我的脚本碰巧运行它的任何终端的termcaps,这意味着我最终得到ANSI颜色代码以及\r s。 / p>


当然,无论哪种方式,我都会收到数百条无用的线路,但我认为这是你想要的? (或者您可能希望使用universal_newlines='\r''\r'翻译为'\n'?这有点作弊,因为这是自我覆盖的Unix终端输出,您可以使用重新假装它的经典Mac输出......但它确实有效。)

答案 1 :(得分:0)

我遇到了类似的问题,但我也想要实时进度,因为 git clone 需要很长时间(比如 5 分钟),因为它是一个大型 git 存储库。我想向用户提供实时反馈。

这是一个 Python 3.7 工作示例:

# This will print stdout/stderr as it comes in
def run_shell_command_with_realtime_output(shell_command_string, working_dir='.'):
    # print the command to be executed, just for fun
    print("run_shell_command >", shell_command_string)

    # run it, this will NOT block
    sub_process = subprocess.Popen(shell_command_string,
                                   shell=True,
                                   cwd=working_dir, universal_newlines=True,
                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # print the stdout/stderr as it comes in
    while True:
        # The readline() will block until...
        # it reads and returns a string that ends in a '\n',
        # or until the process has ended which will result in '' string
        output = sub_process.stdout.readline()
        if output:
            print(output.strip())
        elif sub_process.poll() is not None:
            break

    # get the return code
    return_code = sub_process.wait()

    # Was there an error?
    if return_code != 0:
        print("FYI, the subprocess had an error, you might want to do something special...")

    # return the sub_process, in case the caller wants to check exit/return codes
    return sub_process

事实证明 git clone 似乎不像我们通常习惯的那样写入 stdout/stderr。相反,它使用分页器,这就是它更新同一行的方式,就像克隆时一样,并且 % 在同一行上递增。

所以你需要这样称呼它。

# prepare to clone the git repo
git_clone_url_with_credentials = "https://<username>:<password>@bitbucket.org/myreponame.git"
git_clone_with_progress_cmd = "git --no-pager clone --progress {}".format(git_clone_url_with_credentials)
helpers_misc.run_shell_command_with_progress(git_clone_with_progress_cmd)

这应该可以...