Best way to parse command line args in Bash?
I find the use of getopt
to be the easiest. It provides correct handling of arguments which is tricky otherwise. For example, getopt
will know how to handle arguments to a long option specified on the command line as --arg=option
or --arg option
.
What is useful in parsing any input passed to a shell script is the use of the "$@"
variables. See the bash man page for how this differs from $@
. It ensures that you can process arguments that include spaces.
Here's an example of how I might write s script to parse some simple command line arguments:
#!/bin/bashargs=$(getopt -l "searchpath:" -o "s:h" -- "$@")eval set -- "$args"while [ $# -ge 1 ]; do case "$1" in --) # No more options left. shift break ;; -s|--searchpath) searchpath="$2" shift ;; -h) echo "Display some help" exit 0 ;; esac shiftdoneecho "searchpath: $searchpath"echo "remaining args: $*"
And used like this to show that spaces and quotes are preserved:
user@machine:~/bin$ ./getopt_test --searchpath "File with spaces and \"quotes\"."searchpath: File with spaces and "quotes".remaining args: other args
Some basic information about the use of getopt
can be found here
If you want to avoid using getopt
you can use this nice quick approach:
- Defining help with all options as
##
comments (customise as you wish). - Define for each option a function with same name.
- Copy the last five lines of this script to your script (the magic).
Example script: log.sh
#!/bin/sh## $PROG 1.0 - Print logs [2017-10-01]## Compatible with bash and dash/POSIX## ## Usage: $PROG [OPTION...] [COMMAND]...## Options:## -i, --log-info Set log level to info (default)## -q, --log-quiet Set log level to quiet## -l, --log MESSAGE Log a message## Commands:## -h, --help Displays this help and exists## -v, --version Displays output version and exists## Examples:## $PROG -i myscrip-simple.sh > myscript-full.sh## $PROG -r myscrip-full.sh > myscript-simple.shPROG=${0##*/}LOG=infodie() { echo $@ >&2; exit 2; }log_info() { LOG=info}log_quiet() { LOG=quiet}log() { [ $LOG = info ] && echo "$1"; return 1 ## number of args used}help() { grep "^##" "$0" | sed -e "s/^...//" -e "s/\$PROG/$PROG/g"; exit 0}version() { help | head -1}[ $# = 0 ] && helpwhile [ $# -gt 0 ]; do CMD=$(grep -m 1 -Po "^## *$1, --\K[^= ]*|^##.* --\K${1#--}(?:[= ])" log.sh | sed -e "s/-/_/g") if [ -z "$CMD" ]; then echo "ERROR: Command '$1' not supported"; exit 1; fi shift; eval "$CMD" $@ || shift $? 2> /dev/nulldone
Testing
Running this command:
./log.sh --log yep --log-quiet -l nop -i -l yes
Produces this output:
yepyes
By the way: It's compatible with posix!