getopt, getopts or manual parsing - what to use when I want to support both short and long options?
getopt
vs getopts
seems to be a religious issue. As for the arguments against getopt
in the Bash FAQ:
"
getopt
cannot handle empty arguments strings" seems to refer to a known issue with optional arguments, which it looks likegetopts
doesn't support at all (at least from readinghelp getopts
for Bash 4.2.24). Fromman getopt
:getopt(3) can parse long options with optional arguments that are given an empty optional argument (but can not do this for short options). This getopt(1) treats optional arguments that are empty as if they were not present.
I don't know where the "getopt
cannot handle [...] arguments with embedded whitespace" comes from, but let's test it:
test.sh:
#!/usr/bin/env bash set -o errexit -o noclobber -o nounset -o pipefail params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "$@")" eval set -- "$params" while true do case "$1" in -a|--alpha) echo alpha shift ;; -b|--bravo) echo "bravo=$2" shift 2 ;; -c|--charlie) echo charlie shift ;; --) shift break ;; *) echo "Not implemented: $1" >&2 exit 1 ;; esac done
run:
$ ./test.sh - $ ./test.sh -acb ' whitespace FTW ' alpha charlie bravo= whitespace FTW $ ./test.sh -ab '' -c alpha bravo= charlie $ ./test.sh --alpha --bravo ' whitespace FTW ' --charlie alpha bravo= whitespace FTW charlie
Looks like check and mate to me, but I'm sure someone will show how I completely misunderstood the sentence. Of course the portability issue still stands; you'll have to decide how much time is worth investing in platforms with an older or no Bash available. My own tip is to use the YAGNI and KISS guidelines - Only develop for those specific platforms which you know are going to be used. Shell code portability generally goes to 100% as development time goes to infinity.
There's this getopts_long written as a POSIX shell function that you may embed inside your script.
Note that the Linux getopt
(from util-linux
) works correctly when not in traditional mode and supports long options, but is probably not an option for you if you need to be portable to other Unices.
Recent versions of ksh93 (getopts
) and zsh (zparseopts
) have built-in support for parsing long options which might be an option for you as those are available for most Unices (though often not installed by default).
Another option would be to use perl
and its Getopt::Long
module both of which should be available on most Unices nowadays, either by writing the whole script in perl
or just call perl just to parse the option and feed the extracted information to the shell. Something like:
parsed_ops=$(
perl -MGetopt::Long -le '
@options = (
"foo=s", "bar", "neg!"
);
Getopt::Long::Configure "bundling";
$q="'\''";
GetOptions(@options) or exit 1;
for (map /(\w+)/, @options) {
eval "\$o=\$opt_$_";
$o =~ s/$q/$q\\$q$q/g;
print "opt_$_=$q$o$q"
}' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...
See perldoc Getopt::Long
for what it can do and how it differs from other option parsers.
If it has to be portable to a range of Unices, you'd have to stick to POSIX sh. And AFAIU there you just have no choice but rolling argument handling by hand.