I wish to have long and short forms of command line options invoked using my shell script.
I know that getopts
can be used, but like in Perl, I have not
There are three implementations that may be considered:
Bash builtin getopts
. This does not support long option names with the double-dash prefix. It only supports single-character options.
BSD UNIX implementation of standalone getopt
command (which is what MacOS uses). This does not support long options either.
GNU implementation of standalone getopt
. GNU getopt(3) (used by the command-line getopt(1) on Linux) supports parsing long options.
Some other answers show a solution for using the bash builtin getopts
to mimic long options. That solution actually makes a short option whose character is "-". So you get "--" as the flag. Then anything following that becomes OPTARG, and you test the OPTARG with a nested case
.
This is clever, but it comes with caveats:
getopts
can't enforce the opt spec. It can't return errors if the user supplies an invalid option. You have to do your own error-checking as you parse OPTARG.So while it is possible to write more code to work around the lack of support for long options, this is a lot more work and partially defeats the purpose of using a getopt parser to simplify your code.
Here you can find a few different approaches for complex option parsing in bash: http://mywiki.wooledge.org/ComplexOptionParsing
I did create the following one, and I think it's a good one, because it's minimal code and both long and short options work. A long option can also have multiple arguments with this approach.
#!/bin/bash
# Uses bash extensions. Not portable as written.
declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
case "${opt}" in
-) #OPTARG is name-of-long-option or name-of-long-option=value
if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
then
opt=${OPTARG/=*/}
OPTARG=${OPTARG#*=}
((OPTIND--))
else #with this --key value1 value2 format multiple arguments are possible
opt="$OPTARG"
OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
fi
((OPTIND+=longoptspec[$opt]))
continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
;;
loglevel)
loglevel=$OPTARG
;;
h|help)
echo "usage: $0 [--loglevel[=]<value>]" >&2
exit 2
;;
esac
break; done
done
# End of file
Take a look at shFlags which is a portable shell library (meaning: sh, bash, dash, ksh, zsh on Linux, Solaris, etc.).
It makes adding new flags as simple as adding one line to your script, and it provides an auto generated usage function.
Here is a simple Hello, world!
using shFlag:
#!/bin/sh
# source shflags from current directory
. ./shflags
# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'
# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# say hello
echo "Hello, ${FLAGS_name}!"
For OSes that have the enhanced getopt that supports long options (e.g. Linux), you can do:
$ ./hello_world.sh --name Kate
Hello, Kate!
For the rest, you must use the short option:
$ ./hello_world.sh -n Kate
Hello, Kate!
Adding a new flag is as simple as adding a new DEFINE_ call
.
If all your long options have unique, and matching, first characters as the short options, so for example
./slamm --chaos 23 --plenty test -quiet
Is the same as
./slamm -c 23 -p test -q
You can use this before getopts to rewrite $args:
# change long options to short options
for arg; do
[[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
if [ "${arg:0:2}" == "--" ];
then args="${args} -${arg:2:1}"
else args="${args} ${delim}${arg}${delim}"
fi
done
# reset the incoming args
eval set -- $args
# proceed as usual
while getopts ":b:la:h" OPTION; do
.....
Thanks for mtvee for the inspiration ;-)
Th built-in OS X (BSD) getopt does not support long options, but the GNU version does: brew install gnu-getopt
. Then, something similar to: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt
.
Another way...
# translate long options to short
for arg
do
delim=""
case "$arg" in
--help) args="${args}-h ";;
--verbose) args="${args}-v ";;
--config) args="${args}-c ";;
# pass through anything else
*) [[ "${arg:0:1}" == "-" ]] || delim="\""
args="${args}${delim}${arg}${delim} ";;
esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
case $opt in
h) usage ;;
v) VERBOSE=true ;;
c) source $OPTARG ;;
\?) usage ;;
:)
echo "option -$OPTARG requires an argument"
usage
;;
esac
done