A simple ksh framework prototype
Par Olivier Mengué le lundi 5 novembre 2007, 23:59 - Code - Lien permanent
Nothing is as good as a old Unix shell for running and monitoring batchs. Here is a proposition for a simple framework that will improve readabilty of the code while increasing error reporting in the output.
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:
#!/bin/ksh ./my-program1 -x || ./myprogram2 -p 1 "$@"
to:
#!/usr/bin/perl 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 startsEND return_code
: just after a task stopsSTEP title command
: launch a task, withBEGIN
andEND
.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:
#!/bin/ksh . ./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 END STEP T4 err T4 2 END echo Never reached.>&2
And the output:
$ ./steps-proto.ksh Enable log BEGIN Prog BEGIN Tests BEGIN T1 Test T1 : succès END T1: 0 BEGIN T2 Test T2 : échec 1 END T2: 1 BEGIN T3 Test T3 : succès END T3: 0 END Tests: 1 BEGIN T4 Test T4 : échec 2 END T4: 2 END Prog: 3 EXIT 3 Disable log $ echo $? 3
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 fi # Push the title on the stack step_stack[depth]="$title" # Status accumulator for sub-steps initialized to 0 (OK) step_status[depth]=0 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 fi # Reports status to parent step STEP_STATUS $status } # Args: # $1 Status code function ABORT { END "$@" while [[ ${#step_stack[*]} -gt 1 ]] do END done } # Args: # $1 Task name # $@ Command function STEP { BEGIN "$1" shift "$@" 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
- ...