Catching external program exceptions

Ben Finney bignose+hates-spam at benfinney.id.au
Wed Jan 2 01:53:37 CET 2008


Marty <martyb1 at earthlink.net> writes:

> 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.

    <URL:http://docs.python.org/lib/os-process.html#l2h-2761>

> from the corresponding os.system() call, i.e. "os.system('mount
> test')", but it doesn't work as expected:
> 
> 
> >>> import os, sys
> >>> try: os.system('mount test')
> ... 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
>> 8) == 1).

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.

-- 
 \       "I bought some batteries, but they weren't included; so I had |
  `\                             to buy them again."  -- Steven Wright |
_o__)                                                                  |
Ben Finney



More information about the Python-list mailing list