views:

1185

answers:

4

In a related question, I asked where to find the documentation for the C function "wait." This was an attempt to figure out return codes for the commands.getstatusoutput() module. Stackoverflow came through, but the documentation didn't help. Here's what puzzles me:

#!/usr/bin/python
import commands
goodcommand = 'ls /'
badcommand = 'ls /fail'
status, output = commands.getstatusoutput(goodcommand)
print('Good command reported status of %s' % status)
status, output = commands.getstatusoutput(badcommand)
print('Bad command reported status of %s' % status)

When run on OS X (Leopard) I get the following output: (Which matches the documentation.)

$ python waitest.py 
Good command reported status of 0
Bad command reported status of 256

On OS X, doing an "ls /fail ; echo $?" gets the following output:

$ ls /fail ; echo $?
ls: /fail: No such file or directory
1

When run on Linux (Ubuntu Hardy) I get the following output:

$ python waitest.py 
Good command reported status of 0
Bad command reported status of 512

On Ubuntu, doing "ls /fail" gets a 2:

$ ls /fail ; echo $?
ls: cannot access /fail: No such file or directory
2

So Python appears to be multiplying status codes by 256. Huh? Is this documented somewhere?

A: 

Wow. The insight that it was multiplying by 256 got me there. Searching for "python commands +256" got me to a Python Module Of The Week article which explains what's going on.

Here's a snippet from that page:

The function getstatusoutput() runs a command via the shell and returns the exit code and the text output (stdout and stderr combined). The exit codes are the same as for the C function wait() or os.wait(). The code is a 16-bit number. The low byte contains the signal number that killed the process. When the signal is zero, the high byte is the exit status of the program. If a core file was produced, the high bit of the low byte is set.

And some of Doug's code:

from commands import *

def run_command(cmd):
    print 'Running: "%s"' % cmd
    status, text = getstatusoutput(cmd)
    exit_code = status >> 8
    signal_num = status % 256
    print 'Signal: %d' % signal_num
    print 'Exit  : %d' % exit_code
    print 'Core? : %s' % bool(exit_code / 256)
    print 'Output:'
    print text
    print

run_command('ls -l *.py')
run_command('ls -l *.notthere')
run_command('echo "WAITING TO BE KILLED"; read input')
Schof
+2  A: 

Looking at commands.py:

def getstatusoutput(cmd):
    """Return (status, output) of executing cmd in a shell."""
    import os
    pipe = os.popen('{ ' + cmd + '; } 2>&1', 'r')
    text = pipe.read()
    sts = pipe.close()
    if sts is None: sts = 0
    if text[-1:] == '\n': text = text[:-1]
    return sts, text

We see sts holds the value of os.popen(...).close(). Looking at that documentation, os.popen(...).close() returns the value of os.wait:

os.wait()

Wait for completion of a child process, and return a tuple containing its pid and exit status indication: 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); the high bit of the low byte is set if a core file was produced. Availability: Unix.

Emphasis was mine. I agree that this "encoding" isn't terribly intuitive, but at least it was fairly obvious at a glance that it was being multiplied/bit-shifted.

Mark Rushakoff
+3  A: 

There is a set of functions in os module (os.WIFCONTINUED, os.WIFSTOPPED, os.WTERMSIG, os.WCOREDUMP, os.WIFEXITED, os.WEXITSTATUS, os.WIFSIGNALED, os.WSTOPSIG), which correspond to macros from wait(2) manual. You should use them to interpret the status code.

For example, to get the exit code you should use os.WEXITSTATUS(status)

A better idea would be to switch to subprocess module.

abbot
A: 

I think the code detection is incorrect.

"If a core file was produced, the high bit of the low byte is set." means 128.

so I think the core line should be

print 'Core? : %s' % bool(status & 128)

Felix Labrecque