I often find myself writing a lot of bash scripts that wrap functionality of my services and programs in ways that make my job easier. While bash doesn’t lend itself well to traditional “inheriting” of program elements, it still is very helpful to build a toolbox of snippets, templates, and bits of script that let me assemble new scripts with tried and true methods to get things done.
One of the hardest things to do well in bash is parsing command line options in a consistent cross platform way. The template below shows an example of how to do this that allows for short, long and positional options.
The template uses getopt to normalize short options after parsing long options in a two pass approach. Using getopt transforms options into their canonical form – for example, the option string “-c foobar -laht -v” after being parsed by getopt, becomes “-c foobar -l -a -h -t -v”, allowing them to be handled more consistently.
The getopt string (stored in the variable “opts” below) consists of all the short option letters, with letters that take an argument (e.g. cmd -a foo) being followed by a colon. So if I had short options “a” and “b”, and “b” took an argument, my getopt string would be “ab:”.
# Option defaults OPT="value" # Gets the command name without path cmd(){ echo `basename $0`; } # Help command output usage(){ echo "\ `cmd` [OPTION...] -f, --flag; Set a flag -o, --opt; Set an option with argument (default: $OPT) -v, --verbose; Enable verbose output (include multiple times for more ; verbosity, e.g. -vvv) " | column -t -s ";" } # Error message error(){ echo "`cmd`: invalid option -- '$1'"; echo "Try '`cmd` -h' for more information."; exit 1; } # getopt string opts="fvo:" # There's two passes here. The first pass handles the long options and # any short option that is already in canonical form. The second pass # uses `getopt` to canonicalize any remaining short options and handle # them for pass in 1 2; do while [ -n "$1" ]; do case $1 in --) shift; break;; -*) case $1 in -f|--flag) FLAG=1;; -o|--opt) OPT=$2; shift;; -v|--verbose) VERBOSE=$(($VERBOSE + 1));; --*) error $1;; -*) if [ $pass -eq 1 ]; then ARGS="$ARGS $1"; else error $1; fi;; esac;; *) if [ $pass -eq 1 ]; then ARGS="$ARGS $1"; else error $1; fi;; esac shift done if [ $pass -eq 1 ]; then ARGS=`getopt $opts $ARGS` if [ $? != 0 ]; then usage; exit 2; fi; set -- $ARGS fi done # Handle positional arguments if [ -n "$*" ]; then echo "`cmd`: Extra arguments -- $*" echo "Try '`cmd` -h' for more information." exit 1 fi # Set verbosity if [ "0$VERBOSE" -eq 0 ]; then # Default, quiet elif [ $VERBOSE -eq 1 ]; then # Enable log messages elif [ $VERBOSE -ge 2 ]; then # Enable high verbosity elif [ $VERBOSE -ge 3 ]; then # Enable debug verbosity fi
The other interesting thing is that this template allows for multiple verbose flags (-v, -vv, -vvv, etc.) to specify different levels of verbosity – which can be a very handy thing for debugging a misbehaving script or controlling output from a subcommand.
Have other good tips for bash scripting? Share them in the comments!
View this template as a gist on github: https://gist.github.com/2765260