I'm trying to catch a very simple error, but with no result for now.
I've created a tag on my Git repository and trying to catch an error with the creation of existing tag. My code looks like:
try:
check_call(['git', 'tag', '-a', '1.2.3', '-m', 'test tag.'])
except CalledProcessError as err:
print err.output, err.returncode, err.message
I can't catch a message: tag '1.2.3' already exists.
It raises in my git command, but I can't catch it.
I was also trying to replace check_call with check_output, but it also doesn't help.
Using just a returncode is not enough for me, because it raises 128 for this case and many others (I want to have separate handling for different issues).
Ideas?
You need to use check_output() to get the output. The reason you do not get that working, is because the error message is not written to stdout but to stderr. This can easily be taken care of by redirecting stderrto stdout. Using the following should work for you:
from subprocess import check_output, STDOUT, CalledProcessError
try:
check_output(['git', 'tag', '-a', '1.2.3', '-m', 'test tag.'], stderr=STDOUT)
except CalledProcessError as err:
print err.output, err.returncode, err.message
Here you can see that I have used check_output() and redirected stderr .
Rewriting the answer.
import subprocess
command = ['git', 'tag', '-a', '1.2.3', '-m', 'test tag.']
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = p.stdout.read()
p.communicate()
if p.returncode == 0:
handleSuccess() # supposed you have such a function
else:
# you can handle any error based on the output of git
if 'tag' in output and 'already exists' in output:
handleTagAlreadyExistsError() # supposed you have such a function
Related
I have a script that is executing 5 different shell commands and I'm using subprocess.check_call() to execute them. The problem is that I can't seem to figure out how to properly capture and analyze the return code.
According to the docs The CalledProcessError object will have the return code in the returncode attribute., but I don't understand how to access that. If I say
rc = subprocess.check_call("command that fails")
print(rc)
It tells me
subprocess.CalledProcessError: Command 'command that fails' returned non-zero exit status 1
But I can't figure out how to capture just the integer output of 1.
I'd imagine this must be doable somehow?
With check_call you'll have to add a try/except block and access the exception. With subprocess.run you can access the result without a try/except block.
import subprocess
try:
subprocess.check_call(["command", "that", "fails"])
except subprocess.CalledProcessError as e:
print(e.returncode)
Or using subprocess.run:
result = subprocess.run(["command", "that", "fails"])
print(result.returncode)
Whenever the subprocess.check_call method fails is raises a CalledProcessError. From the docs:
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)
Run command with arguments. Wait for command to complete. If the
return code was zero then return, otherwise raise CalledProcessError.
The CalledProcessError object will have the return code in the
returncode attribute.
You may just want subprocess.run or to use a try/except block to handle the CalledProcessError
perhaps
rc = subprocess.run("some_cmd").returncode
or
try
...
rc = subprocess.check_call("command that fails")
except CalledProcessError as error:
rc = error.returncode
I'm trying to run a process with subprocess and print its entire output if and only if an exception occurs.
Where I was before:
try:
proc = subprocess.run(
command,
capture_output=True,
check=True,
text=True,
)
except subprocess.CalledProcessError as error:
print(error.output)
This did not work.
Output when subprocess.CalledProcessError occurs:
b''
Replacing capture_output with stdout=subprocess.PIPE resulted in the output of everything regardless whether an exception occurred or not, error.output was still empty.
So I experimented:
This prints everything I would see if I executed the command in the command-line.
subprocess.run(
command,
stdout=subprocess.PIPE,
)
This prints out nothing.
proc = subprocess.run(
command,
capture_output=True,
)
print(proc.stdout.decode())
I also tried subprocess.check_output() which to my information does the same as subprocess.run() with the flags I set in the first code snippet.
What am I missing here? Thanks.
Addendum
import subprocess
command = ['pandoc', 'file']
try:
proc = subprocess.run(
command,
capture_output=True,
check=True,
)
except subprocess.CalledProcessError as error:
print('Exception:')
print(error.output)
This is an MWE with the specific process I want to run (pandoc)
Output
$ pandoc file
pandoc: file: openBinaryFile: does not exist (No such file or directory)
$ ./samplecode.py
Exception:
b''
So the exception gets triggered, but the output object is empty.
It seems that the error message is present in error.stderr and not in error.output. I tried your example (with a ls of non-existent file) :
import subprocess
command = ['ls', 'file']
try:
proc = subprocess.run(
command,
check=True,
capture_output=True,
text=True
)
except subprocess.CalledProcessError as error:
print('Exception:')
print('output : ' + error.output)
print('stderr : ' + error.stderr)
The output is the following :
Exception:
output :
stderr : ls: file: No such file or directory
Hope it helps.
I believe what you're meaning to run is stderr=subprocess.PIPE. This should print the relevant error code to the standard console error output.
Example:
process = subprocess.Popen(['ls', 'myfile.txt'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output,error) = process.communicate()
if error:
print error
The output of subprocess.check_output() looks like this at the moment:
CalledProcessError: Command '['foo', ...]' returned non-zero exit status 1
Is there a way to get a better error message?
I want to see stdout and stderr.
Redirect STDERR to STDOUT.
Example from the interpreter:
>>> try:
... subprocess.check_output(['ls','-j'], stderr=subprocess.STDOUT)
... except subprocess.CalledProcessError as e:
... print('error>', e.output, '<')
...
Will throw:
error> b"ls: invalid option -- 'j'\nTry `ls --help' for more information.\n" <
Explantion
From check_output documentation:
To also capture standard error in the result, use
stderr=subprocess.STDOUT
Don't use check_output(), use Popen and Popen.communicate() instead:
>>> proc = subprocess.Popen(['cmd', '--optional-switch'])
>>> output, errors = proc.communicate()
Here output is data from stdout and errors is data from stderr.
Since I don't want to write more code, just to get a good error message, I wrote subx
From the docs:
subprocess.check_output() vs subx.call()
Look, compare, think and decide what message helps your more.
subprocess.check_output()::
CalledProcessError: Command '['cat', 'some-file']' returned non-zero exit status 1
sub.call()::
SubprocessError: Command '['cat', 'some-file']' returned non-zero exit status 1:
stdout='' stderr='cat: some-file: No such file or directory'
... especially if the code fails in a production environment where
reproducing the error is not easy, subx can call help you to spot the
source of the failure.
In my opinion that a perfect scenario to use sys.excepthook! You just have to filter what you would like to be formatted as you want in the if statement. With this solution, it will cover every exception of your code without having to refract everything!
#!/usr/bin/env python
import sys
import subprocess
# Create the exception handler function
def my_excepthook(type, value, traceback):
# Check if the exception type name is CalledProcessError
if type.__name__ == "CalledProcessError":
# Format the error properly
sys.stderr.write("Error: " + type.__name__ + "\nCommand: " + value.cmd + "\nOutput: " + value.output.strip())
# Else we format the exception normally
else:
sys.stderr.write(str(value))
# We attach every exceptions to the function my_excepthook
sys.excepthook = my_excepthook
# We duplicate the exception
subprocess.check_output("dir /f",shell=True,stderr=subprocess.STDOUT)
You can modify the output as you wish, here is the actual ouput:
Error: CalledProcessError
Command: dir /f
Output: Invalid switch - "f".
I am calling the executable from python script using sub process call. these are the following code I have used:
try:
p = subprocess.Popen([abc.exe], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
except Exception as e:
print str(e)
from abc.exe, I have return 1 in failure case and return 0 for success case. But I don't know how to check the return value in python script.
thanks,
Popen.returncode contains the return code when the process has terminated. You can ensure that using Popen.wait.
You've saved as p the output from .communicate(), not Popen object. Perhaps try:
try:
p = subprocess.Popen(['abc.exe'], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError as e:
print str(e)
stdoutdata, stderrdata = p.communicate()
retcode = p.returncode
Another way to do this is to use subprocess.check_output() since you mention Python 2.7. This runs the command with the same arguments as Popen. The output of the command is returned as a string. If the command returns a non-zero value, a subprocess.CalledProcessError exception is raised.
So I think you can rework your code to something like this:
try:
output = subprocess.check_output(['abc.exe'], shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as ex:
# an error occurred
retcode = ex.returncode
output = ex.output
else:
# no error occurred
process(output)
Note that you can't use the stdout argument in check_output since it is used internally. Here are the docs.
am sure there is a better way to write the below code...below are the issues am running into with my current code, please provide any inputs you may have
I want to check the stderr of below command and re-run it based on the error message
Error mesage looks like "error: Cannot delete the branch "alphanumeric string" which you are currently on,am trying to match like below but running into error
import subprocess
def main():
change="205739"
proc = subprocess.Popen(['git', 'branch', '-d', change], stderr=subprocess.PIPE)
out, error = proc.communicate()
if error.startswith("error: Cannot delete the branch"):
subprocess.check_call(['git', 'branch', '-d', change])
if __name__ == '__main__':
main()
You really want to avoid using shell=True, split it out into a list instead and you'll save yourself having to use interpolation to boot.
To test for equality, use ==; = is for assignment only which is not allowed in a if statement.
You need to use .Popen() if you wanted to check for stderr output:
import subprocess
def main():
change="205739"
proc = subprocess.Popen(['git', 'branch', '-d', change], stderr=subprocess.PIPE)
out, error = proc.communicate()
if error.startswith("error: Cannot delete the branch"):
subprocess.check_call(['git', 'branch', '-d', change])