How to handle OS errors in python - 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.

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

How to catch the errors of a child process using Python subprocess?

I have the following Python(2.7) code:
try:
FNULL = open(os.devnull,'w')
subprocess.check_call(["tar", "-czvf", '/folder/archive.tar.gz', '/folder/some_other_folder'], stdout=FNULL, stderr=subprocess.STDOUT)
except Exception as e:
print str(e)
The problem which I face is that, when there is no more space for the archive, print str(e) prints Command '['tar', '-czvf', '/folder/archive.tar.gz', '/folder/some_other_folder']' returned non-zero exit status 1, which is true, but I want to catch the real error here, that is gzip: write error: No space left on device (I got the this error when I ran the same tar comand manually). Is that possible somehow? I assume that gzip is another process within tar. Am I wrong? Please keep in mind that upgrading to Python 3 is not possible.
EDIT: I also tried to use subprocess.check_output() and print the contents of e.output but that also didn't work
Python 3 solution for sane people
On Python 3, the solution is simple, and you should be using Python 3 for new code anyway (Python 2.7 ended all support nearly a year ago):
The problem is that the program is echoing the error to stderr, so check_output doesn't capture it (either normally, or in the CalledProcessError). The best solution is to use subprocess.run (which check_call/check_output are just a thin wrapper over) and ensure you capture both stdout and stderr. The simplest approach is:
try:
subprocess.run(["tar", "-czvf", '/folder/archive.tar.gz', '/folder/some_other_folder'],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
# ^ Ignores stdout ^ Captures stderr so e.stderr is populated if needed
except CalledProcessError as e:
print("tar exited with exit status {}:".format(e.returncode), e.stderr, file=sys.stderr)
Python 2 solution for people who like unsupported software
If you must do this on Python 2, you have to handle it all yourself by manually invoking Popen, as none of the high level functions available there will cover you (CalledProcessError didn't spawn a stderr attribute until 3.5, because no high-level API that raised it was designed to handle stderr at all):
with open(os.devnull, 'wb') as f:
proc = subprocess.Popen(["tar", "-czvf", '/folder/archive.tar.gz', '/folder/some_other_folder'],
stdout=f, stderr=subprocess.PIPE)
_, stderr = proc.communicate()
if proc.returncode != 0:
# Assumes from __future__ import print_function at top of file
# because Python 2 print statements are terrible
print("tar exited with exit status {}:".format(proc.returncode), stderr, file=sys.stderr)

How to distinguish between subprocess errors in 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

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.

How should I wrap an interactive subprocess (eg. shell) in Python

I'm writing a simple wrapper module in Python3 for the adb binary using the subprocess module, however the 'shell' command can either run single, one-shot commands or with no arguments run an interactive shell.
At some point I (or someone else) may use something like Vte to leverage this in a GUI, but I'm lost as to what is sane for my function to return, or if I should even be using Popen in this instance.
I chose to use the subprocess module when I implemented a wrapper for ADB in python. I found that the check_output(...) function came in handy because it would verify the command would return with a 0 status. If the command executed by check_output(...) returns a non-zero status a CalledProcessError is thrown. I found this convenient as I could than report back to the user a specific ADB command failed to run.
Here is a snippet of how I implemented the method. Feel free to reference my implementation of the ADB wrapper.
def _run_command(self, cmd):
"""
Execute an adb command via the subprocess module. If the process exits with
a exit status of zero, the output is encapsulated into a ADBCommandResult and
returned. Otherwise, an ADBExecutionError is thrown.
"""
try:
output = check_output(cmd, stderr=subprocess.STDOUT)
return ADBCommandResult(0,output)
except CalledProcessError as e:
raise ADBProcessError(e.cmd, e.returncode, e.output)

Categories

Resources