Even if I master more advanced scripting languages such as Perl or Python, when your task is mainly to run external programs and check return code, those languages make the code a bit harder to read.

I prefer:

./my-program1 -x || ./myprogram2 -p 1 "$@"


system './my-program1', '-x' || system './my-program2', '-p', 1, @ARGV;

Of course that was a trivial sample and real programs are always more complex. But with a bit of abstraction you can keep your program readable and still powerful with strict error checking.

That was my aim when writing this simple framework that make easier the logging of a sequence of tasks. This is just an experimentation but you may still find it useful.

The aims of the framework are:

  • to transparently handle exhaustive error reporting of every failing command : error codes are tracked
  • to bubble errors up to the top level to report any error code to the calling program
  • to give improved readability of the program and of its output (the log)
  • to be flexible : let the frameork user handle errors as he wants ; the framework should be just some help, not a prison, so let the user interact with the framework

The API uses a few words to border your tasks and monitors tasks result code:

  • BEGIN title: just before a task starts
  • END return_code: just after a task stops
  • STEP title command: launch a task, with BEGIN and END.
  • ABORT return_code: report a fatal error and exit the program, bubling the error code up to the caller

Here is a sample shell program using this framework:


. ./steps.lib.ksh

function err
        echo "Test $1 : échec $2"
        return $2

function ok
        echo "Test $1 : succès"
        return 0

BEGIN "Prog"

  BEGIN Tests

    STEP "T1" ok  T1
    STEP "T2" err T2 1
    STEP "T3" ok  T3


  STEP T4   err   T4 2


echo Never reached.>&2

And the output:

$ ./steps-proto.ksh 
Enable log
Test T1 : succès
END T1: 0
Test T2 : échec 1
END T2: 1
Test T3 : succès
END T3: 0
END Tests: 1
Test T4 : échec 2
END T4: 2
END Prog: 3
Disable log
$ echo $? 

And here the framework itself. The implementation uses a stack implemented using ksh arrays to keep track of the depth level of the BEGIN/END blocks.

# vim:set ft=sh:

# steps.lib.ksh
# Copyright Olivier Mengué 2007

# Args:
#  $1  Title
function BEGIN
        typeset title="$1"
        typeset -i depth=${#step_stack[*]}
        if [[ depth -eq 0 ]] ; then
                echo Enable log

        # Push the title on the stack

        # Status accumulator for sub-steps initialized to 0 (OK)

        echo BEGIN "$title"

# Args:
#  $1  Status code
function END
        typeset -i depth=${#step_stack[*]}-1

        # Default value for status is the OR-accumulation of the sub-steps
        typeset -i status=${1:-${step_status[depth]}}

        typeset title="${step_stack[depth]}"
        echo END "$title: $status"

        unset step_stack[depth]

        # Drops the status of the sub-steps
        unset step_status[depth]

        # If stack is now empty, exit
        if [[ depth -eq 0 ]] ; then
                echo EXIT $status
                echo Disable log
                exit $status

        # Reports status to parent step
        STEP_STATUS $status

# Args:
#  $1  Status code
function ABORT
        END "$@"
        while [[ ${#step_stack[*]} -gt 1 ]]

# Args:
#  $1  Task name
#  $@  Command
function STEP
        BEGIN "$1"
        END $?

# Args:
#  $1  Status code
function STEP_STATUS
        typeset -i status=$1
        # Accumulates step status in the current context with OR
        [[ ${#step_status[*]} -gt 0 ]] && let step_status[${#step_status[*]}-1]\|=status
        return status

This is just a proof of concept. Once your program uses this kind of framework, you can just now modify the framework to change the output without touching the program anymore. If your program is critical, you probably want to avoid to change it (typos are a big problem in shell scripting). Thanks to a framework and a good test suite for the framework you can improve your program by changing the framework and still avoid any breakage (it depends on the quality of your test suite).

Here are some suggestions:

  • use indentation dependending of the depth level 
  • use different output style when printing the task/result BEGIN/END depending on the depth
  • ...