如何从 Python subprocess.check_output() 捕获异常输出?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24849998/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to catch exception output from Python subprocess.check_output()?
提问by kramer65
I'm trying to do a Bitcoin payment from within Python. In bash I would normally do this:
我正在尝试从 Python 中进行比特币支付。在 bash 中,我通常会这样做:
bitcoin sendtoaddress <bitcoin address> <amount>
so for example:
所以例如:
bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214
if it is successfull I get a transaction id as output but if I try to transfer an amount larger than my bitcoin balance, I get the following output:
如果成功,我会得到一个交易 ID 作为输出,但如果我尝试转移大于我的比特币余额的金额,我会得到以下输出:
error: {"code":-4,"message":"Insufficient funds"}
In my Python program I now try to do the payment as follows:
在我的 Python 程序中,我现在尝试按如下方式付款:
import subprocess
try:
output = subprocess.check_output(['bitcoin', 'sendtoaddress', address, str(amount)])
except:
print "Unexpected error:", sys.exc_info()
If there's enough balance it works fine, but if there's not enough balance sys.exc_info()
prints out this:
如果有足够的余额它工作正常,但如果没有足够的余额sys.exc_info()
打印出来:
(<class 'subprocess.CalledProcessError'>, CalledProcessError(), <traceback object at 0x7f339599ac68>)
It doesn't include the error which I get on the command line though. So my question is; how can I get the outputted error ({"code":-4,"message":"Insufficient funds"}
) from within Python?
它不包括我在命令行上得到的错误。所以我的问题是;如何{"code":-4,"message":"Insufficient funds"}
从 Python 中获取输出的错误 ( )?
All tips are welcome!
欢迎所有提示!
采纳答案by Ferdinand Beyer
According to the subprocess.check_output()
docs, the exception raised on error has an output
attribute that you can use to access the error details:
根据subprocess.check_output()
docs,错误引发的异常具有output
可用于访问错误详细信息的属性:
try:
subprocess.check_output(...)
except subprocess.CalledProcessError as e:
print(e.output)
You should then be able to analyse this string and parse the error details with the json
module:
然后,您应该能够分析此字符串并使用json
模块解析错误详细信息:
if e.output.startswith('error: {'):
error = json.loads(e.output[7:]) # Skip "error: "
print(error['code'])
print(error['message'])
回答by jfs
Trying to "transfer an amount larger than my bitcoin balance" is not an unexpected error. You could use Popen.communicate()
directly instead of check_output()
to avoid raising an exception unnecessarily:
尝试“转移大于我的比特币余额的金额”并不是一个意外错误。您可以Popen.communicate()
直接使用而不是check_output()
避免不必要地引发异常:
from subprocess import Popen, PIPE
p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE)
output = p.communicate()[0]
if p.returncode != 0:
print("bitcoin failed %d %s" % (p.returncode, output))
回答by Phil R
I don't think the accepted solution handles the case where the error text is reported on stderr. From my testing the exception's output attribute did not contain the results from stderr and the docs warn against using stderr=PIPE in check_output(). Instead, I would suggest one small improvement to J.F Sebastian's solution by adding stderr support. We are, after all, trying to handle errors and stderr is where they are often reported.
我认为接受的解决方案不能处理在 stderr 上报告错误文本的情况。从我的测试来看,异常的输出属性不包含来自 stderr 的结果,并且文档警告不要在 check_output() 中使用 stderr=PIPE。相反,我建议通过添加 stderr 支持对 JF Sebastian 的解决方案进行一个小的改进。毕竟,我们正在尝试处理错误,而 stderr 是它们经常被报告的地方。
from subprocess import Popen, PIPE
p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
if p.returncode != 0:
print("bitcoin failed %d %s %s" % (p.returncode, output, error))
回答by macetw
There are good answers here, but in these answers, there has not been an answer that comes up with the text from the stack-trace output, which is the default behavior of an exception.
这里有很好的答案,但在这些答案中,没有一个答案与堆栈跟踪输出中的文本一起出现,这是异常的默认行为。
If you wish to use that formatted traceback information, you might wish to:
如果您希望使用格式化的回溯信息,您可能希望:
import traceback
try:
check_call( args )
except CalledProcessError:
tb = traceback.format_exc()
tb = tb.replace(passwd, "******")
print(tb)
exit(1)
As you might be able to tell, the above is useful in case you have a password in the check_call( args ) that you wish to prevent from displaying.
正如您可能知道的那样,如果您希望阻止显示 check_call( args ) 中有密码,则上述内容很有用。
回答by Felipe Buccioni
Based on the answer of @macetw I print the exception directly to stderr in a decorator.
根据@macetw 的回答,我将异常直接打印到装饰器中的 stderr。
Python 3
蟒蛇 3
from functools import wraps
from sys import stderr
from traceback import format_exc
from typing import Callable, Collection, Any, Mapping
def force_error_output(func: Callable):
@wraps(func)
def forced_error_output(*args: Collection[Any], **kwargs: Mapping[str, Any]):
nonlocal func
try:
func(*args, **kwargs)
except Exception as exception:
stderr.write(format_exc())
stderr.write("\n")
stderr.flush()
raise exception
return forced_error_output
Python 2
蟒蛇 2
from functools import wraps
from sys import stderr
from traceback import format_exc
def force_error_output(func):
@wraps(func)
def forced_error_output(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception as exception:
stderr.write(format_exc())
stderr.write("\n")
stderr.flush()
raise exception
return forced_error_output
Then in your worker just use the decorator
然后在你的工人中使用装饰器
@force_error_output
def da_worker(arg1: int, arg2: str):
pass
回答by Blasco
As mentioned by @Sebastian the default solution should aim to use run()
:
https://docs.python.org/3/library/subprocess.html#subprocess.run
正如@Sebastian 提到的,默认解决方案应该旨在使用run()
:https:
//docs.python.org/3/library/subprocess.html#subprocess.run
Here a convenient implementation (feel free to change the log class with print statements or what ever other logging functionality you are using):
这是一个方便的实现(随意使用打印语句或您正在使用的其他日志记录功能更改日志类):
import subprocess
def _run_command(command):
log.debug("Command: {}".format(command))
result = subprocess.run(command, shell=True, capture_output=True)
if result.stderr:
raise subprocess.CalledProcessError(
returncode = result.returncode,
cmd = result.args,
stderr = result.stderr
)
if result.stdout:
log.debug("Command Result: {}".format(result.stdout.decode('utf-8')))
return result
And sample usage (code is unrelated, but I think it serves as example of how readable and easy to work with errors it is with this simple implementation):
和示例用法(代码无关,但我认为它可以作为示例,说明使用这个简单的实现是多么可读和易于处理错误):
try:
# Unlock PIN Card
_run_command(
"sudo qmicli --device=/dev/cdc-wdm0 -p --uim-verify-pin=PIN1,{}"
.format(pin)
)
except subprocess.CalledProcessError as error:
if "couldn't verify PIN" in error.stderr.decode("utf-8"):
log.error(
"SIM card could not be unlocked. "
"Either the PIN is wrong or the card is not properly connected. "
"Resetting module..."
)
_reset_4g_hat()
return