【问题标题】:python3 subprocess error capturing output from shell commandspython3子进程错误捕获shell命令的输出
【发布时间】:2020-05-17 01:48:30
【问题描述】:

我已经关注了一些关于捕获子流程输出的 SO 问题。在子进程中使用 git 似乎输出到错误而不是 stdout 或 stderr

这就是我想从 shell 复制的内容。

git add .
git commit -m 'commit message blah'
git push -u origin master

跟踪每个阶段的输出。 在此示例中,我没有要提交的更新。

$git add .
$ git commit -m 'wip'
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

按照下面的代码,我正在使用

output = run("git add .", check=True, capture_output=True, shell=True)

并检查 output.returncode,但遇到 CalledProcessError 而不是要输出的值。 有任何想法吗?我可以通过错误捕获来伪造功能,但这远非最佳。

我无法从 'git push' 捕获“没有提交,工作树干净”的输出,这看起来很奇怪。 '' stderr & stdout 的值也是如此。 'git commit' 之后的 returncode 是 0。

nb: 'git 添加。'不会在 shell 中产生任何输出。

环境:Windows 10 上的 ubuntu 应用程序。Python3,anaconda 64 位。

from subprocess import *
import sys
try:
  print("start git add.")
  #output = run("git add .", check=True, stdout=PIPE, stderr=PIPE, shell=True)
  output = run("git add .", check=True, capture_output=True, shell=True)
  print("git add completed.")
  print("output.stderr:", "'"+output.stderr.decode('utf-8')+"'")
  print("output.stdout:", "'"+output.stdout.decode('utf-8')+"'")
  print("output.stderr==None:", output.stderr==None)
  print("output.stdout==None:", output.stdout==None)
  print("output.returncode:", output.returncode)
  if output.returncode==0:
    output=None
    print("start git commit.")
    #output = run(f"git commit -m 'commit message three'", check=True, stdout=PIPE, stderr=PIPE, shell=True)
    output = run("git commit -m 'commit message three'", check=True, capture_output=True, shell=True)
    #exits to exception here when error occurs dueing git commit.
    print("git commit completed.")
    print("output.stderr:", output.stderr.decode('utf-8'))
    print("output.stdout:", output.stdout.decode('utf-8'))
    print("output.stderr==None:", output.stderr==None)
    print("output.stdout==None:", output.stdout==None)
    print("output.returncode:", output.returncode)
    if output.returncode==0:
      output=None
      print("start git push.")
      #output = run("git push -u origin master -f", check=True, stdout=PIPE, stderr=PIPE, shell=True)
      output = run("git push -u origin master -f", capture_output=True)
      print("git push completed.")
      print("output.stderr:", output.stderr)
      print("output.stdout:", output.stdout)
      print("output.stderr==None:", output.stderr==None)
      print("output.stdout==None:", output.stdout==None)
      print("output.returncode:", output.returncode)
      if output.returncode==0:
        print("success @ git push, output.returncode==0.")
except CalledProcessError as error:
  print("CalledProcessError error caught.")
  print('CalledProcessError:An exception occurred: {}'.format(error))
  print("CalledProcessError:sys.exc_info()[0]:", sys.exc_info()[0])
  print("CalledProcessError:output:", output)
  print("CalledProcessError:output.stderr:", output.stderr)
  print("CalledProcessError:output.stdout:", output.stdout)

此代码块的输出

start git add.
git add completed.
output.stderr: ''
output.stdout: ''
output.stderr==None: False
output.stdout==None: False
output.returncode: 0
start git commit.
CalledProcessError error caught.
CalledProcessError:An exception occurred: Command 'git commit -m 'commit message three'' returned non-zero exit status 1.
CalledProcessError:sys.exc_info()[0]: <class 'subprocess.CalledProcessError'>
CalledProcessError:output: None
Traceback (most recent call last):
  File "<stdin>", line 15, in <module>
  File "/home/bmt/anaconda3/lib/python3.7/subprocess.py", line 487, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command 'git commit -m 'commit message three'' returned non-zero exit status 1.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 41, in <module>
AttributeError: 'NoneType' object has no attribute 'stderr'

编辑:我的主要错误是使用 'check=True' 和 'shell=True' 还将 shell 命令从一个字符串更改为字符串列表。

output = run("git add .", check=True, capture_output=True, shell=True)

output = run(["git", "add", "."], capture_output=True)

【问题讨论】:

  • 这不是 Python。写入标准错误的是 Git。您可能期望在标准输出上做的许多事情实际上都在标准错误中。并非所有事情都进入 stderr,只有许多你意想不到的事情。
  • 然而,作为一般规则,您不应从任何程序调用任何 interactive Git 命令。 Git 由许多工具构建而成,其中许多工具非常适合从其他程序运行。使用这些工具,以非交互方式运行它们,以获得可预测的、可编程的结果。其中大部分可以在 Git 本身中得到更好的处理,但是 git addgit commit 都可以以非交互方式使用,而且你已经在做这部分了。

标签: python-3.x git


【解决方案1】:

我添加了一些关于使用来自其他编程语言的 Git 作为 cmets 的通用 cmets。但是,对于您在此处调用git commit 的更具体的情况,请注意您的错误CalledProcessError 发生因为 git commit 没有发现任何可提交的内容。当您从 shell 执行此操作时:

$ git commit -m 'wip'
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

git commit 命令返回了一个失败退出状态,值为 1。您的 shell 没有对此抱怨。

当你运行同样的事情时:

output = run("git commit -m 'commit message three'", check=True,
    capture_output=True, shell=True)

git commit 命令再次返回失败退出状态。 Python,而不是 Git——特别是 the subprocess.run function——引发了 CalledProcessError,它这样做是因为你包含了 check=True

此异常跳出正常的代码流并进入您的except CalledProcessError 部分,您尝试使用通过名为output 的变量找到的值。此变量最后一次绑定到此处的值:

    output=None
    print("start git commit.")

变量still此时包含None,这就是你看到的原因:

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 41, in <module>
AttributeError: 'NoneType' object has no attribute 'stderr'

作为一般规则,1 如果您想使用subprocess.run 捕获所有内容(包括退出代码以及stdout 和stderr 流),请不要设置check=True,因为这意味着如果退出代码不是零,则跳出正常处理,引发 CalledProcessError。也就是说,您是否写过:

output = run("git commit -m 'commit message three'", capture_output=True,
    shell=True)

整个事情的表现会更加符合您的预期,但您应该查阅 output.returncode 值以查看 git commit 是否成功。

还要注意,shell=True 通常是个坏主意:它往往会引入安全漏洞。 The Python documentation calls this out. 在这种情况下你可以使用:

output = run(["git", "commit", "-m", "commit message three"], capture_output=True)

这避免了对shell=True 的需要。 (您在这里的特定示例是安全的,因为整个字符串是一个常量,但这是一个不好的习惯。)

最后,虽然git addgit commit 可以以非交互方式相当安全地运行——您只需检查它们的返回码值以确定成功或失败——git push 更棘手。这里的问题是git push 要求 your(本地)Git 调用其他(远程)Git。 Git 通常会使用 https/ssl 或 ssh 来执行此操作。为此,Git 会调用其他第三方程序,这些程序有时会要求输入密码或密码。

因为 Git 可以为此使用多个不同的第三方程序,每个程序都有自己不同的选项,所以没有一种正确的方法可以避免它。有关各种详细信息,请参阅 Git keeps prompting me for a passwordGit push requires username and password


1然而,从 Python 3.5 开始,您可以使用此处引发的错误值来访问所有内容:

try:
    result = subprocess.run(cmd, check=True)
    ... use result.stdout etc ...
except subprocess.CalledProcessError as err:
    ... use err.stdout etc ...

但这通常会导致更多重复代码,因此最好省略check=True并检查result.returncode

【讨论】:

  • 谢谢。这看起来像是参考级别的答案,值得包含在 api 文档中。检查我的实现 atm。
猜你喜欢
  • 1970-01-01
  • 2017-07-05
  • 1970-01-01
  • 1970-01-01
  • 2022-01-14
相关资源
最近更新 更多