Catching external program exceptions

M

Marty

I need to catch exceptions thrown by programs started by the os.system function,
as indicated by a non-zero return code (e.g. the mount utility). For example,
if I get the following results in a bash shell:

$mount test
mount: can't find /home/marty/test in /etc/fstab or /etc/mtab

then I want to catch the same exception from the corresponding os.system() call,
i.e. "os.system('mount test')", but it doesn't work as expected:

.... except: print 'error'
....
mount: can't find /home/marty/test in /etc/fstab or /etc/mtab
256
I get the same results with popon, popen2, popen3, etc. Apparently these also
work only when the program does not generate an exception. Is there any way to
catch the return code. or if not, a workaround?
 
G

Gabriel Genellina

I need to catch exceptions thrown by programs started by the os.system
function,
as indicated by a non-zero return code (e.g. the mount utility). For

Notice that these are two different things. You can wait until the
external program ends, and get its return code; and you can sometimes read
a message from its stderr. But none of these things by itself throws an
exception in Python.
example,
if I get the following results in a bash shell:

$mount test
mount: can't find /home/marty/test in /etc/fstab or /etc/mtab

then I want to catch the same exception from the corresponding
os.system() call,
i.e. "os.system('mount test')", but it doesn't work as expected:

Perhaps it doesn't work as *you* expected, but it does work as specified.
From the os.system description at
http://docs.python.org/lib/os-process.html
"On Unix, the return value is the exit status of the process encoded in
the format specified for wait(). [...] [That return value] is
system-dependent."
Later on the same page, describing the wait function, says that the return
value is "a 16-bit number, whose low byte is the signal number that killed
the process, and whose high byte is the exit status (if the signal number
is zero)"
So os.system does NOT raise an exception when the executed program ends
with a non-zero exit code.
I get the same results with popon, popen2, popen3, etc. Apparently
these also
work only when the program does not generate an exception.

An external program cannot generate an exception inside the Python
program. Only Python itself can do that.
Is there any way to
catch the return code. or if not, a workaround?

From the description above, you could do some math to obtain the exit code
looking at the os.system return value.
But perhaps it's easier to use subprocess.check_call, see "Convenience
functions" at http://docs.python.org/lib/module-subprocess.html
 
B

Ben Finney

Marty said:
I need to catch exceptions thrown by programs started by the os.system
function, as indicated by a non-zero return code (e.g. the mount
utility).

That's not an exception. It's referred to as an "exit status" or
(often) some short form or variation of that term.

Python can make available the exit status value of an external
process, but isn't going to interpret them to the point of raising
exceptions that you can catch.

The exit status is always available to the parent process, but the
*meaning* of any given value of that status is highly dependent on the
program that was running.

If you want to respond to particular values, you'll have to do so by
explicitly testing the exit status against values to which you've
assigned meaning -- hopefully meanings documented in the manual page
for the program which generates the exit status.
For example, if I get the following results in a bash
shell:

$mount test
mount: can't find /home/marty/test in /etc/fstab or /etc/mtab

then I want to catch the same exception

What's happening isn't an exception. It's a message being emitted to
an output stream (likely the stderr stream of the mount process,
though some programs will put error messages on stdout), followed by
an exit of that process.

The parent of that process will receive an exit status from the
process when it terminates; it will also (on Unix-like systems)
receive a separate value indicating the OS signal that caused the
process to exit. Python's 'os.system' function makes both these values
available as the return value of the function.

said:
from the corresponding os.system() call, i.e. "os.system('mount
test')", but it doesn't work as expected:


... except: print 'error'
...
mount: can't find /home/marty/test in /etc/fstab or /etc/mtab
256

The statement within the 'try' block executes the 'os.system()' call;
since you're running inside the interpreter, the return value from
that function is displayed.

The return value, as documented in the 'os.system' documentation,
encodes both the signal number (the low 8 bits, in this case (256 &
0x0F) == 0) and, since the signal number is zero ("no signal
received") the exit status value (the high 8 bits, in this case (256
No exception is being raised, so the 'try' block completes
successfully and the 'except' block is not invoked.


So, instead of testing for an exception, you should instead be testing
the exit status code returned by the 'os.system' call. First, read the
documentation for the command you're running::

$ man mount
[...]

Unfortunately the 'mount(8)' manual page doesn't (on my system)
mention possible values for the exit status. So, you'll just have to
deduce it, or go with the common convention of "zero status ==
success, non-zero status == error".

MountFailedError = OSError
import os
return_value = os.system('mount test')
signal_number = (return_value & 0x0F)
if not signal_number:
exit_status = (return_value >> 8)
if exit_status:
raise MountFailedError("Oh no!")

Why isn't this standardised? Because the process-call interface is
inconsistent between operating systems, and there's even less
consistency in the implementations of the programs that return these
values.

The Zen of Python says: "In the face of ambiguity, refuse the
temptation to guess." Thus, Python can do little more than present the
values it received from the process call; anything further would be
guessing.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,992
Messages
2,570,220
Members
46,807
Latest member
ryef

Latest Threads

Top