繁体   English   中英

如何在运行 git 命令时获取子进程标准输出?

[英]How to get subprocess stdout while running git command?

我有一个用 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的完成百分比。 不需要日志或任何日志文件。

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

当然,当写入终端时,它有很多输出——但这些输出是不断被覆盖的进度条。 通常,您不希望那样——这将是一大堆控制字符和重复的行。

但是,如果您确实想要它,则有两种选择。


首先,您可以使用 PTY(伪 TTY)。 您可以使用os.openpty创建一个 PTY,然后将 PTY 明确地交给子进程。 或者您可以使用os.forkpty ,它处理分叉并自动连接 PTY,因此您所要做的就是调用os.exec函数之一。 或者您可以使用pty模块。 (不完全清楚哪个更便携; openptyforkpty声称pty更便携,从概念上讲它是这样设计的……但它也只在 Linux 上进行了真正的测试。)

请注意, git想要 PTY 作为它的 stderr,而不是它的 stdout。


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

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


当然,无论哪种方式,我都会收到数百条我不感兴趣的无用行,但我认为这就是您想要的? (或者,也许你想使用universal_newlines='\\r'来翻译'\\r''\\n' ?那是稍微欺骗,因为这是自重写Unix的终端输出,而你假装它是经典的Mac输出......但它有效。)

我有一个类似的问题,但我也想要实时进度,因为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)

那应该这样做......

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM