簡體   English   中英

如何從 Python subprocess.check_output() 捕獲異常輸出?

[英]How to catch exception output from Python subprocess.check_output()?

我正在嘗試從 Python 中進行比特幣支付。 在 bash 中,我通常會這樣做:

bitcoin sendtoaddress <bitcoin address> <amount>

例如:

bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214

如果成功,我會得到一個交易 ID 作為輸出,但如果我嘗試轉移一個大於我的比特幣余額的金額,我會得到以下輸出:

error: {"code":-4,"message":"Insufficient funds"}

在我的 Python 程序中,我現在嘗試按如下方式付款:

import subprocess

try:
    output = subprocess.check_output(['bitcoin', 'sendtoaddress', address, str(amount)])
except:
    print "Unexpected error:", sys.exc_info()

如果有足夠的余額,它工作正常,但如果沒有足夠的余額sys.exc_info()打印出這個:

(<class 'subprocess.CalledProcessError'>, CalledProcessError(), <traceback object at 0x7f339599ac68>)

它不包括我在命令行上遇到的錯誤。 所以我的問題是; 如何從 Python 中獲取輸出的錯誤( {"code":-4,"message":"Insufficient funds"} )?

根據subprocess.check_output()文檔,錯誤引發的異常有一個output屬性,您可以使用它來訪問錯誤詳細信息:

try:
    subprocess.check_output(...)
except subprocess.CalledProcessError as e:
    print(e.output)

然后,您應該能夠分析此字符串並使用json模塊解析錯誤詳細信息:

if e.output.startswith('error: {'):
    error = json.loads(e.output[7:]) # Skip "error: "
    print(error['code'])
    print(error['message'])

我認為接受的解決方案不能處理在 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))

嘗試“轉移比我的比特幣余額更大的金額”並不是一個意外的錯誤。 您可以直接使用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))

正如@Sebastian 所提到的,默認解決方案應該旨在使用run()https ://docs.python.org/3/library/subprocess.html#subprocess.run

這是一個方便的實現(隨意使用打印語句或您正在使用的任何其他日志記錄功能更改日志類):

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

和示例用法(代碼是不相關的,但我認為它可以作為一個示例,說明這個簡單實現的可讀性和易於處理錯誤):

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

從 Python 3.5 開始, subprocess.run()支持check參數:

如果檢查為真,並且進程以非零退出代碼退出,則會引發 CalledProcessError 異常。 該異常的屬性包含參數、退出代碼以及標准輸出和標准錯誤(如果它們被捕獲)。

一個簡單的例子,它將引發並打印出CalledProcessError

import subprocess
try:
    subprocess.run("exit 1", shell=True, check=True, timeout=15, capture_output=True)
except subprocess.CalledProcessError as e:
    print(e)  # Output: Command 'exit 1' returned non-zero exit status 1.

這里有很好的答案,但在這些答案中,堆棧跟蹤輸出中的文本沒有給出答案,這是異常的默認行為。

如果您希望使用該格式化的回溯信息,您可能希望:

import traceback

try:
    check_call( args )
except CalledProcessError:
    tb = traceback.format_exc()
    tb = tb.replace(passwd, "******")
    print(tb)
    exit(1)

正如您可能知道的那樣,如果您希望阻止顯示的 check_call( args ) 中有密碼,上述內容很有用。

這對我有用。 它捕獲子進程的所有標准輸出輸出(對於 python 3.8):

from subprocess import check_output, STDOUT
cmd = "Your Command goes here"
try:
    cmd_stdout = check_output(cmd, stderr=STDOUT, shell=True).decode()
except Exception as e:
    print(e.output.decode()) # print out the stdout messages up to the exception
    print(e) # To print out the exception message

根據@macetw 的回答,我將異常直接打印到裝飾器中的stderr。

蟒蛇 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

蟒蛇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

然后在你的工人中使用裝飾器

@force_error_output
def da_worker(arg1: int, arg2: str):
    pass

我認為以前的大多數答案都是正確的,就我而言,我需要在 Windows 服務器上執行此操作,並且命令是 Powershell,因為這對我來說非常有效:

    try:
        
    
        print("inpgoress")           

        cmd_exec="Get-Date"
        print(cmd_aws)

        subprocess.run(['powershell', '-Command', cmd_exec],shell=False,check=True,capture_output=True,text=True,encoding="utf-8")

        

    except Exception as e:
        print(e)
        print("ERROR: something went wrong executing powershell command")
        raise e  

被調用的子進程需要被告知捕獲被調用程序中的輸出並引發異常。 做起來很簡單。

首先,使用

subprocess.run() instead of subprocess.call()

假設您想要名為“Vijay.py”的python 腳本。 要引發異常,請使用以下命令;

subprocess.run("py vijay.py", check=True, capture_output=True, shell=True)

然后可以將上述方法放入 try 和 except 塊中以立即引發錯誤,或者可以使用 sys.exit(1) :任何非零退出都可以

try:
    subprocess.call("py vijay.py", check=True, capture_output=True, shell=True)
except Exception as e:
    print("Exception raised: ", e)

vijay.py的主體可以如下所示;

vijay.py

try:
    Your code is here...
except Exception as e:
    sys.exit(1) // or can even use raise Exception("ur own exception to raise:)
enter code here

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM