"subprocess.Popen" – checking for success and errors
Question:
I want to check if a subprocess has finished execution successfully or failed. Currently I have come up with a solution but I am not sure if it is correct and reliable. Is it guaranteed that every process outputs its errors only to stderr respectfully to stdout
:
Note: I am not interested in just redirecting/printing out the output. That I know already how to do.
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
alternatively:
if "" == pipe.stdout.readline():
print("Success")
self.isCommandExectutionSuccessful = True
else:
print("Error")
self.isCommandExectutionSuccessful = False
and:
if not "" == pipe.stderr.readline():
print("Success")
self.isCommandExectutionSuccessful = True
else:
print("Error")
self.isCommandExectutionSuccessful = False
Answers:
output,error=pipe.communicate()
This will wait for command to finish and give you output or error depending on the state of command.
Do you need to do anything with the output of the process?
The check_call
method might be useful here. See the python docs here: https://docs.python.org/2/library/subprocess.html#subprocess.check_call
You can then use this as follows:
try:
subprocess.check_call(command)
except subprocess.CalledProcessError:
# There was an error - command exited with non-zero code
However, this relies on command
returning an exit code of 0 for succesful completion and a non-zero value for an error.
If you need to capture the output as well, then the check_output
method may be more appropriate. It is still possible to redirect the standard error if you need this as well.
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
See the docs here: https://docs.python.org/2/library/subprocess.html#subprocess.check_output
You can check return code of the process using check_call() method.
In case if process returned non-zero value CalledProcessError will be raised.
This is how I did it finally:
# 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)
I hope it will be useful to someone else.
Complete solution with check on return code, stdout and stderr:
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])
Prints:
retcode = 0
res = (b'bashnbunzip2nbusyboxnbzcatn...zmorenznewn', b'')
stderr = b''
bash
bunzip2
busybox
bzcat
...
zmore
znew
retcode = 2
res = (b'', b"ls: cannot access '/bing': No such file or directoryn")
stderr = b"ls: cannot access '/bing': No such file or directoryn"
All previous answers are correct but here is another way to skin a cat
(this is test with Python 3.9)
cmd_aws="aws s3 cp s3://some_bucket/somefile+" "+"somefile_name"
proc_prep = subprocess.Popen(cmd_aws, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc_prep.communicate()
if err:
print(out)
print(err)
There are situations where using check_call
is not a possibility. For example when you need to communicate with the process (e.g., passing input with communicate
).
In this case, a simple solution is to mimic check_call
manually. We can look at the Python source code to see what check_call
is doing here and you can see it’s simply checking the return code is 0 and if not it raises a CalledProcessError
. It couldn’t be simpler.
You may notice that check_call
is not including stdout or stderr in the CalledProcessError
even though it can accept them. This is because the process may not have captured them unless it was given subprocess.PIPE
for stdout
and stderr
Popen parameters.
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)
The above is an example scenario where we need to PIPE input data (in this case an URL) to the process. It is not possible to write to stdin with check_call
.
We simply mimic check_call
with the last 2 lines.
I want to check if a subprocess has finished execution successfully or failed. Currently I have come up with a solution but I am not sure if it is correct and reliable. Is it guaranteed that every process outputs its errors only to stderr respectfully to stdout
:
Note: I am not interested in just redirecting/printing out the output. That I know already how to do.
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
alternatively:
if "" == pipe.stdout.readline():
print("Success")
self.isCommandExectutionSuccessful = True
else:
print("Error")
self.isCommandExectutionSuccessful = False
and:
if not "" == pipe.stderr.readline():
print("Success")
self.isCommandExectutionSuccessful = True
else:
print("Error")
self.isCommandExectutionSuccessful = False
output,error=pipe.communicate()
This will wait for command to finish and give you output or error depending on the state of command.
Do you need to do anything with the output of the process?
The check_call
method might be useful here. See the python docs here: https://docs.python.org/2/library/subprocess.html#subprocess.check_call
You can then use this as follows:
try:
subprocess.check_call(command)
except subprocess.CalledProcessError:
# There was an error - command exited with non-zero code
However, this relies on command
returning an exit code of 0 for succesful completion and a non-zero value for an error.
If you need to capture the output as well, then the check_output
method may be more appropriate. It is still possible to redirect the standard error if you need this as well.
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
See the docs here: https://docs.python.org/2/library/subprocess.html#subprocess.check_output
You can check return code of the process using check_call() method.
In case if process returned non-zero value CalledProcessError will be raised.
This is how I did it finally:
# 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)
I hope it will be useful to someone else.
Complete solution with check on return code, stdout and stderr:
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])
Prints:
retcode = 0
res = (b'bashnbunzip2nbusyboxnbzcatn...zmorenznewn', b'')
stderr = b''
bash
bunzip2
busybox
bzcat
...
zmore
znew
retcode = 2
res = (b'', b"ls: cannot access '/bing': No such file or directoryn")
stderr = b"ls: cannot access '/bing': No such file or directoryn"
All previous answers are correct but here is another way to skin a cat
(this is test with Python 3.9)
cmd_aws="aws s3 cp s3://some_bucket/somefile+" "+"somefile_name"
proc_prep = subprocess.Popen(cmd_aws, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc_prep.communicate()
if err:
print(out)
print(err)
There are situations where using check_call
is not a possibility. For example when you need to communicate with the process (e.g., passing input with communicate
).
In this case, a simple solution is to mimic check_call
manually. We can look at the Python source code to see what check_call
is doing here and you can see it’s simply checking the return code is 0 and if not it raises a CalledProcessError
. It couldn’t be simpler.
You may notice that check_call
is not including stdout or stderr in the CalledProcessError
even though it can accept them. This is because the process may not have captured them unless it was given subprocess.PIPE
for stdout
and stderr
Popen parameters.
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)
The above is an example scenario where we need to PIPE input data (in this case an URL) to the process. It is not possible to write to stdin with check_call
.
We simply mimic check_call
with the last 2 lines.