signalling via exit status in Python

A common idiom in non-trivial command line tools is to have more than two return codes. For instance, diff uses 0 for ‘same inputs’, 1 for ‘different inputs’, 2 for ‘trouble’.

Doing that in Python is a little harder though, and since I’ve gotten it wrong in the past, I want to write it down for both myself and anyone else contemplating it.

The issue is that both your program and the Python VM itself can fail, and so if you attempt to use a common status code with those the Python VM uses for failures, you have to make sure that the meanings are at least broadly compatible. There’s also a bug in existing Python releases that will cause an exit status of 0 sometimes when an error is actually appropriate.

I’ve only researched this on CPython, its possible that other Python VM’s behave differently, and as far as I know this is not a language spec issue (but perhaps it should be).

tl;dr:

  1. Always flush stdout and stderr yourself, even when signalling errors.
  2. Never use status 1 or 2 for non-error conditions.
  3. (Provisional) don’t use status 120 at all.

Details:

CPython exits with 0 when the interpreter cleanup code fails to flush stdout/stderr, even though that would be an error if it happened earlier. To address that, add an explicit flush of both streams before your program ends. We may end up making CPython exit with 120 when the stdout/err flushing fails. There’s also a possibility that a very early threading error may result in a 0 exit code, though I haven’t managed to make this actually happen yet.

CPython exits with 1 when site.py fails to import, so using 1 for non-error conditions makes it hard for callers to discriminate between your meaning and site.py failures.

Cpython exits with 2 when CLI arguments fail to parse, so using 2 for non-error conditions is similar there. optparse also uses 2 for this, so even if you are using a different interpreter, it is not a safe status code to reuse with different semantics.

 

Leave a comment