How to distinguish between subprocess errors in python? - python

I'm working on a terminal that can call other programs like any other terminal. I'm using subprocess for it, on Windows.
I'm running into 2 issues.
First:
Currently, I'm using OSError for all errors raised when using subprocess.Popen.
The code for it is here:
try:
subprocess.Popen([command])
except OSError:
print("'" + command[0] + "' is not recognised as a command, program or bterm file.")
When I type python, it opens command-line python correctly.
When I type asdfa, it returns the error.
The problem is, when I type python non-existent-file.py I get the same error, when the child argument was the issue.
I want the terminal to return (null): can't open file 'test': [Errno 2] No such file or directory like when it's called from cmd or bash.
How can I distinguish between these 2 errors, while keeping my custom error message for when the file doesn't exist?
Second: Whenever I pass multi-word args into subprocess.Popen or subprocess.call I automatically get that error, which I don't get using os.system()
I don't want to use os.system because I can't raise custom errors with it.
What am I doing wrong?

Exceptions in subprocess calls:
Exceptions raised in the child process, before the new program has started to execute, will be re-raised in the parent.
Additionally, the exception object will have one extra attribute called child_traceback, which is a string containing traceback information from the child’s point of view.
The most common exception raised is OSError.
This occurs, for example, when trying to execute a non-existent file. Applications should prepare for OSError exceptions.
A ValueError will be raised if Popen is called with invalid arguments.
check_call() and check_output() will raise CalledProcessError if the called process returns a non-zero return code.
You can find more at:
https://docs.python.org/2/library/subprocess.html#exceptions
As well as you can find the Exception Heirarchy at:
https://docs.python.org/2/library/exceptions.html#exception-hierarchy
try:
output = subprocess.check_output("\\test.exe')
except subprocess.CalledProcessError as e:
print("Something Fishy... returncode: " + e.returncode + ", output:\n" + e.output)
else:
print("Working Fine:\n" + output)

You could test for the existence of the executable first with the help of shutil.which.
if shutil.which(commands[0]):
try:
subprocess.Popen([commands])
except OSError as err:
print(err)
else:
print("'{}' is not recognised as a command, program or bterm file.".format(commands[0])
The documentation has a great deal of info however: https://docs.python.org/dev/library/subprocess.html which may be helpful.
Edit: showed how to capture output, thanks to Auxilor

Related

Python subprocess — how to ignore exit code warnings?

I am trying to display the final results.txt file via default program. I've tried with bare Popen() without run() and got the same effect. The target file is opening (for me it's the see mode) but after exiting it I receive:
Warning: program returned non-zero exit code #256
Is there any way to ignore it and prevent my program from displaying such warning? I don't care about it because it's the last thing the program does, so I don't want people to waste their time clicking Enter each time...
Code's below:
from subprocess import run, Popen
if filepath[len(filepath)-1] != '/':
try:
results = run(Popen(['start', 'results.txt'], shell=True), stdout=None, shell=True, check=False)
except TypeError:
pass
else:
try:
results = run(Popen(['open', 'results.txt']), stdout=None, check=False)
except TypeError:
pass
except FileNotFoundError:
try:
results = run(Popen(['see', 'results.txt']), stdout=None, check=False)
except TypeError:
pass
except FileNotFoundError:
pass
Your immediate error is that you are mixing subprocess.run with subprocess.Popen. The correct syntax is
y = subprocess.Popen(['command', 'argument'])
or
x = subprocess.run(['command', 'argument'])
but you are incorrectly combining them into, effectively
x = subprocess.run(subprocess.Popen(['command', 'argument']), shell=True)
where the shell=True is a separate bug in its own right (though it will weirdly work on Windows).
What happens is that Popen runs successfully, but then you try to run run on the result, which of course is not a valid command at all.
You want to prefer subprocess.run() over subprocess.Popen in this scenario; the latter is for hand-wiring your own low-level functionality in scenarios where run doesn't offer enough flexibility (such as when you require the subprocess to run in parallel with your Python program as an independent process).
Your approach seems vaguely flawed for Unix-like systems; you probably want to run xdg-open if it's available, otherwise the value of os.environ["PAGER"] if it's defined, else fall back to less, else try more. Some ancient systems also have a default pager called pg.
You will definitely want to add check=True to actually make sure your command fails properly if the command cannot be found, which is the diametrical opposite of what you appear to be asking. With this keyword parameter, Python checks whether the command worked, and will raise an exception if not. (In its absence, failures will be silently ignored, in general.) You should never catch every possible exception; instead, trap just the one you really know how to handle.
Okay, I've achieved my goal with a different approach. I didn't need to handle such exception, I did it without the subprocess module.
Question closed, here's the final code (it looks even better):
from os import system
from platform import system as sysname
if sysname() == 'Windows':
system('start results.txt')
elif sysname() == 'Linux':
system('see results.txt')
elif sysname() == 'Darwin':
system('open results.txt')
else:
pass

Batch file not receiving the ERRORLEVEL from the python script it launches

I have a batch file running a python script.
This python script always ends using sys.exit(code) no matter what, with code being an error code.
The batch file doesn't get my error code and instead always reads 0 with the following:
..\..\STS\Python\python.exe Version_code_ID.py %*
echo Error level = %ERRORLEVEL%
This instalation of python is in 3.7.1.
I know for sure my python code exited with other codes than 0 thanks to my logger (and me voluntarily causing errors for testing purpose).
For example I had it exit with 1 and 12, both attempts resulting in getting 0 in the batch.
Just in case, here is also the python function I use to exit:
def exit(code):
if(code > 0 ):
log.error("The computing module will shutdown with the following error code : "+str(code))
elif(code == 0):
log.info("Execution sucessfull")
log.info("STOP of VERSION_CODE_ID Computing Module")
log.removeHandler(stream_handler)
log.removeHandler(file_handler)
stream_handler.close()
file_handler.close()
sys.exit(code)
log is just the name of my logging.handlers logger.
Any idea what might be causing this problem?
Turns out the problem was that the bulk of my python script was in this try clause:
try:
#All of my code
except SystemExit:
()
except Exception as ex:
fun.log.error('ERROR: Unknown exception: ' + repr(ex))
I originally added the
except SystemExit:
()
because I thought SystemExit showing-up as an "exception" in the console was a problem, but it's not.
In short the solution was to remove that except.
The reason seem to be that catching the systemExit, as the name implies, doesn't let it get sent to the batch, which then believes no error code was sent.
Surprisingly for me except Exception as ex: doesn't catch the systemExit made by sys.exit(). That's great in my case since I still need it to log unknown exceptions.

How to run function in python after exiting command line

I want to run a function after I "command c" on terminal. I am scraping some data onto a file using python, and then I want to immediately close and push the file to google drive when I terminate the scraping process.I have written two different files for scraping and pushing but I would like to do this all in one file. How do I indicate the change of "command c" in python? If command c..do this...
Any help would be super useful! Thanks!
The sequence ctrl+C (or command+C on Mac) causes a KeyboardInterrupt exception to be raised.
You can simply catch this exception, with a simple try-except.
For instance, the following code will run a dummy work that takes some time, and on a ctrl+C, will stop that work and print its current state.
import time
try:
for i in range(100):
time.sleep(1)
except KeyboardInterrupt:
print(i)
Note that, as specified in the doc, the KeyboardInterrupt exception inherits from BaseException but not from Exception.
As a consequence, a except Exception clause would not catch a KeyboardInterrupt, while a except BaseException would catch both KeyboardInterrupt and Exception.
What you need is atexit module https://docs.python.org/2/library/atexit.html
create a handler function to do the persisting part. register it with atexit to run at the exit.
def goodbye(name, adjective):
print 'Goodbye, %s, it was %s to meet you.' % (name, adjective)
import atexit
atexit.register(goodbye, 'Donny', 'nice')

How to handle OS errors in python

I am trying to create linux groups using python.Below is function for creating groups.
def createGroups(self):
adminGroupCommand="groupadd "+ self.projectName+'admin'
userGroupCommand="groupadd "+ self.projectName+'user'
try:
os.system(adminGroupCommand)
except OSError as err:
print("group already exists: "+adminGroupCommand)
try:
os.system(userGroupCommand)
except OSError as err:
print("group already exists: "+userGroupCommand)
The function is successfully creating groups.But if i run the same again it gives below output when run,
groupadd: group 'akphaadmin' already exists
groupadd: group 'akphauser' already exists
Its not printing my custom message in "except"block.How can i force the function to print custom message when creating groups if they already exists.
The function os.system does not report errors from the command line in that way. If you're lucky you'll get the return code (that should be zero on success), but you cannot rely on that according to the documentation.
Instead the documentation recommends to use the subprocess module instead:
def createGroups(self):
adminGroupCommand="groupadd "+ self.projectName+'admin'
userGroupCommand="groupadd "+ self.projectName+'user'
try:
subprocess.check_call(adminGroupCommand, shell=True)
except subprocess.CalledProcessError:
print("group already exists: "+adminGroupCommand)
try:
subprocess.check_call(userGroupCommand, shell=True)
except subprocess.CalledProcessError as err:
print("group already exists: "+userGroupCommand)
The CalledProcessError has an attribute returncode that you can examine if the groupadd command has different return codes for different causes of failure.
Also note that shell=True means that you rely on the command interpreter to forward the return code (which may not always the case). Instead you could call the command directly:
adminGroupCommand=["groupadd", self.projectName, 'admin']
...
try:
subprocess.check_call(adminGroupCommand)
...which also has the benefit that if self.projectName contains spaces, asterisks (or other characters that the command line interpreter might interpret) they will be sent to the command in unmodified form (as single command line argument).
Another benefit in using subprocess is that you can control where the output of the command is being directed. If you want to discard stderr or stdout you can redirect it to /dev/null without relying on a shell to do that for you:
subprocess.check_call(adminGroupCommand, stderr=os.devnull, stdout=os.devnull)
There are also possibility to redirect to subprocess.PIPE which allows your python code to read the standard output or standard error from the subprocess.

Unable to run command without shell=True in subprocess command

I am developing a small tool with python in Linux. Earlier I was using Python 2.7 but now I changed it to Python 3.4 to see if it could help in solving my problem. When I am giving the following code:
try:
x=subprocess.check_output(command, shell=True, timeout=3)
except subprocess.TimeoutExpired as exc:
print ("Timeout bro")
exit()
except Exception as e:
msg = "Some issues in fetching details"
print (msg)
Since the command fetches details from another device and the device is not functioning properly, it is getting timed out after 3 secs and printing the message "Timeout bro". I read the security issues with using shell=True and therefore I made it shell=False for one time and for the second I removed that argument.
try:
x=subprocess.check_output(command, shell=False, timeout=3)
except subprocess.TimeoutExpired as exc:
print ("Timeout bro")
exit()
except Exception as e:
msg = "Some issues in fetching details"
print (msg)
I read at various places that the command works equally well with shell=False. But as soon as I run the above code with shell=False the code directly prints "Some issues in fetching details" without waiting for 3 secs. Is there any way through which I can run the code without shell=True? Please help. Thanks!
When using shell=True, the command may be a string. When using shell=False, the command should be a list of strings, with the first string being the executable, and the subsequent strings being arguments to be passed to the executable.
You might try splitting the command with shlex.split:
import shlex
x = subprocess.check_output(shlex.split(command), shell=False, timeout=3)
By default, when posix=True, shlex.split drops backslashes. So if shlex.split does not work with your command, you may need to use posix=False or split the command manually.
Try splitting the command with command.split(). A string will work in case of shell=True but for shell=False it expects a list of args. However, beware that split won't work in some cases like if you have space in a path etc. I suggest using shlex in that case.

Categories

Resources