Argument Parsing in Shell Scripts
So, say you are writing a shell script (sh or bash) that needs to take arguments like so:
./script.sh start
./script.sh -v start
./script.sh -c foo.conf start
./script.sh -vc foo.conf start
This took me a bit of doing. First, I tried getopt but apparently all that does is disambiguate -v from -c. That, and the FreeBSD man page claims to have improved the example by making it completely inscrutable.
Eventually I found a reference that explained how to use getopts, which worked a lot better, but all the examples I could find didn’t tell you how to reset $@.
Eventually, I figured out that that last bit is accomplished with shift.
Here’s example code:
#!/bin/sh
#
conf=default.conf
while getopts c:hv o
do case "$o" in
c) conf="$OPTARG";;
h) echo "Usage: $0 -hv -c <config> [start|stop|restart|status]" && exit 1;;
v) verbose="yes";;
\?) echo "Usage: $0 -hv -c <config> [start|stop|restart|status]" && exit 1;;
esac
done
# Reset $@
shift `echo $OPTIND-1 | bc`
echo "conf: $conf"
echo "command: $1"
Notes:
- The while loop iterates
$othrough$@where it conforms to the getopts configuration. - getopts c:hv indicates that -h and -v are boolean arguments, and -c is followed by a string argument.
- $OPTIND is set at the end of getopt, and indicates how many arguments through $@ it parsed.
- Oh, and the shift operation resets
$@by $OPTIND-1, so that$1ends up pointing at the start command, or whatever. - If you’re programming in ksh, you can just set
$OPTIND-1. But if you’re programming in ksh you are far beyond needing to check my blog for argument parsing.
Responses
Andrew Ho
My standard sh command line parsing loop looks like this:
while [ $# != 0 ]; do flag="$1" case "$flag" in -a) echo "You provided the -a flag, which takes no arguments" ;; -b) if [ $# -gt 1 ]; then arg="$2" shift else echo "You did not provide an argument for the -b flag" fi echo "You supplied an argument for the -b flag: $arg" ;; *) echo "Unrecognized flag or argument: $flag" ;; esac shift doneIf you are using bash instead of sh, you can accept both -fvalue and -f value variants:
-b*) arg="${arg##-b}" echo "You attached an argument to the -b flag: $arg" ;;My code doesn’t reset
$@. I have a README file in my homedir that reminds me of the other neat bash-ism that I like to use:${1+"$@"}expands to$@, but if$@is empty, it expands to an empty list (different from “$@” which would expand to the single empty string “”).an_agent
FAI…
shift $((OPTIND - 1))works in bash (not sh) without having to pipe to bc.
Ted Henry
Thanks for this post! This is exactly what I needed :-)
Alexander Brankov
Hi guys,
These are very good examples! Thank you to all of you.
It is comprehensive and easy but here is a different case:
myscript.sh --first-argument=23 --second-argument="some string" unnamed argumentsI could not find anything to cook up a good example for this case.
Your competent help would be much appreciated!
Many thanks,
Alex
dannyman
Alexander,
The code in my post handles spaces just fine:
Alexander Brankov
Hi Guys,
It is the equal sign that I am worried about. In this case I can have something like:
./my_script.sh –option_1=”Some Text!” –option-2=10
I struggle to handle this case when I am converting some tools that use this way of calling the commands.
Kind regards,
Alex
Alexander Brankov
Again the two dashes in front of the arguments names ware merged.
Comment
Tiny Print:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>