子进程.Popen——检查成功与错误

52

我想检查一个子进程是否已经成功执行或者失败了。目前我想出了一种解决方案,但我不确定它是否正确可靠。每个进程保证只将其错误输出到标准错误流(stderr)而不是标准输出流(stdout)吗?

注:我不仅仅是想重定向/打印输出。我已经知道如何做了。

pipe = subprocess.Popen(command,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                universal_newlines=True)

if "" == pipe.stdout.readline():
    print("Success")
    self.isCommandExectutionSuccessful = True

if not "" == pipe.stderr.readline():
    print("Error")
    self.isCommandExectutionSuccessful = True

或者:

   if "" == pipe.stdout.readline():
       print("Success")
       self.isCommandExectutionSuccessful = True
   else:
       print("Error")
       self.isCommandExectutionSuccessful = False

并且:

   if not "" == pipe.stderr.readline():
       print("Success")
       self.isCommandExectutionSuccessful = True
   else:
       print("Error")
       self.isCommandExectutionSuccessful = False
6个回答

48

你需要对进程的输出做些什么吗?

check_call方法可能会在这里有用。请参阅Python文档:https://docs.python.org/2/library/subprocess.html#subprocess.check_call

然后你可以按如下方式使用它:

try:
  subprocess.check_call(command)
except subprocess.CalledProcessError:
  # There was an error - command exited with non-zero code

然而,这要求command返回0作为成功完成的退出码,非零值表示错误。

如果您还需要捕获输出,则可能更适合使用check_output方法。如果需要,仍然可以重定向标准错误。

try:
  proc = subprocess.check_output(command, stderr=subprocess.STDOUT)
  # do something with output
except subprocess.CalledProcessError:
  # There was an error - command exited with non-zero code

查看文档请点击此处:https://docs.python.org/2/library/subprocess.html#subprocess.check_output


1
实际上,我将stdout和stderr输出到一个窗口,然后需要检查是否有错误,并设置一个布尔变量,该变量在程序的另一部分中使用。 我尝试使用returncode,但无论成功还是错误,它都为None。 - Zingam
2
好的。还有check_output方法。您可以从中获取stdout和stderr,仍然可以捕获错误。请参见此处:https://docs.python.org/2/library/subprocess.html#subprocess.check_output - elParaguayo

24

具备返回代码、标准输出和标准错误检查的完整解决方案:

import subprocess as sp

# ok
pipe = sp.Popen( 'ls /bin', shell=True, stdout=sp.PIPE, stderr=sp.PIPE )
# res = tuple (stdout, stderr)
res = pipe.communicate()
print("retcode =", pipe.returncode)
print("res =", res)
print("stderr =", res[1])
for line in res[0].decode(encoding='utf-8').split('\n'):
  print(line)

# with error
pipe = sp.Popen( 'ls /bing', shell=True, stdout=sp.PIPE, stderr=sp.PIPE )
res = pipe.communicate()
print("retcode =", pipe.returncode)
print("res =", res)
print("stderr =", res[1])

输出:

retcode = 0
res = (b'bash\nbunzip2\nbusybox\nbzcat\n...zmore\nznew\n', b'')
stderr = b''
bash
bunzip2
busybox
bzcat
...
zmore
znew

retcode = 2
res = (b'', b"ls: cannot access '/bing': No such file or directory\n")
stderr = b"ls: cannot access '/bing': No such file or directory\n"

这里可能的返回值有哪些?是否可能设置该代码...我正在尝试找出是否有任何方法可以知道另一个进程是否能够在终止之前设置返回代码或stderr,以便我可以检查并优雅地清理。 - mike01010
1
@mike01010 返回代码是由进程通过其exit()调用设置的,通常情况下如果没有错误则为0,如果遵循Unix惯例,则为errno在出现错误的情况下。如果您控制源代码,则可以通过exit()自定义它,否则您需要在“退出状态”部分检查命令的man条目。 - PJ_Finnegan

4
      output,error=pipe.communicate()

这将等待命令完成,并根据命令的状态提供输出或错误信息。

3
有时候,使用check_call是不可能的。比如说当你需要与进程通信(例如使用communicate传递输入)的时候。
在这种情况下,一个简单的解决方案是手动模仿check_call。我们可以查看Python源代码来了解check_call这里所做的事情,你会发现它只是检查返回值是否为0,如果不是则会引发CalledProcessError。这再简单不过了。
你可能会注意到,check_call没有在CalledProcessError中包含stdout或stderr,尽管它可以接受它们。这是因为除非给stdoutstderr Popen参数传递subprocess.PIPE,否则进程可能没有捕获它们。
video_url = "http://.../sample/BigBuckBunny.mp4"

p = subprocess.Popen(
    [
        "ffplay",
        "-i", "-"
    ],
    stdin=subprocess.PIPE
)

p.communicate(video_url.encode())
if p.returncode != 0:
    raise subprocess.CalledProcessError(p.returncode, p.args)

以上是一个需要将输入数据(在本例中为URL)传输到进程中的示例场景。使用check_call无法写入标准输入(stdin)。

我们只需模仿最后两行代码即可实现check_call的功能。


3
您可以使用check_call()方法检查进程的返回代码。如果进程返回非零值,将引发CalledProcessError异常。

2
@Zingam: p.returncode is None 表示进程仍在运行。注意:在 p.communicate() 之后,p.returncode 不可能是 None - jfs

0

这是我最终的做法:

    # Call a system process
    try:
        # universal_newlines - makes manual decoding of subprocess.stdout unnecessary
        output = subprocess.check_output(command,
                                         stderr=subprocess.STDOUT,
                                         universal_newlines=True)

        # Print out command's standard output (elegant)
        self.textEdit_CommandLineOutput.insertPlainText(output)
        self.isCommandExecutionSuccessful = True

    except subprocess.CalledProcessError as error:
        self.isCommandExecutionSuccessful = False

        errorMessage = ">>> Error while executing:\n"\
                       + command\
                       + "\n>>> Returned with error:\n"\
                       + str(error.output)
        self.textEdit_CommandLineOutput.append(errorMessage)

        QMessageBox.critical(None,
                             "ERROR",
                             errorMessage)
        print("Error: " + errorMessage)

    except FileNotFoundError as error:
        errorMessage = error.strerror
        QMessageBox.critical(None,
                             "ERROR",
                             errorMessage)
        print("Error: ", errorMessage)

我希望它对其他人有用。


check_output() 返回所有输出作为单个字符串。在 Python 中,for character in some_string 会产生字符而不是行。此外,在子进程运行时,您的 GUI 将被冻结。您应该使用线程或 async.io 来避免阻塞 GUI。请参见链接中的代码示例。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接