【问题标题】:How to get subprocess stdout while running git command?运行 git 命令时如何获取子进程标准输出?
【发布时间】:2014-09-23 03:47:03
【问题描述】:

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

我尝试了一些代码 sn-p。有些适用于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。 有没有办法解决这个问题? 提前致谢!

更新1: 面对现实,我只想获得git clone 的完成百分比。不需要日志或任何日志文件。

【问题讨论】:

标签: python git subprocess


【解决方案1】:

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

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

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


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

注意git 想要 PTY 作为它的标准错误,而不是它的标准输出。


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

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


当然,无论哪种方式,我都会收到数百条我不感兴趣的无用行,但我认为这就是您想要的? (或者您可能想使用universal_newlines='\r''\r' 转换为'\n'?这有点作弊,因为这是自我覆盖的Unix 终端输出,并且您假装它是经典的Mac 输出……但它确实有效。 )

【讨论】:

  • 感谢您的建议。我认为 PTY 解决方案在跨平台上会很痛苦?我的程序必须在 Windows 7 和 MacOS 上运行。
  • @RobinZhang:在 Windows 上无法使用 PTY。 (Windows 确实有类似的概念,但 Python 并没有尝试将这两个概念包装在一个 API 中。)所以我想--progress 是唯一的选择。但首先:问题实际上是否首先发生在 Windows 上?许多命令行应用程序在 Windows 上根本不执行 isatty 检查,这意味着您可以只在 Unix 上使用 PTY 而在 Windows 上什么都不做……
  • 谢谢! --progress 是我想要的!
【解决方案2】:

我遇到了类似的问题,但我也想要实时进度,因为 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 似乎不像我们通常习惯的那样写入标准输出/标准错误。相反,它使用寻呼机,这就是它更新同一行的方式,就像它在克隆时一样,并且 % 在同一行上递增。

所以你需要这样称呼它。

# 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)

应该这样做......

【讨论】:

    猜你喜欢
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    • 2017-03-28
    • 2019-02-28
    • 1970-01-01
    • 2020-05-26
    • 1970-01-01
    • 2020-03-03
    相关资源
    最近更新 更多