I already know about getopts, and this is fine, but it is annoying that you have to have a flag even for mandatory arguments.
Ideally, I\'d like to be able to have a scr
And I heard a completely opposite thing, that you shouldn't use getopt
, but the getopts
builtin.
Cross-platform getopt for a shell script
Never use getopt(1). getopt cannot handle empty arguments strings, or arguments with embedded whitespace. Please forget that it ever existed.
The POSIX shell (and others) offer getopts which is safe to use instead.
Here's yet another way to "Option-ize your shell scripts" (whithout using getopt or getopts):
http://bsdpants.blogspot.com/2007/02/option-ize-your-shell-scripts.html
You're are suffering from illusions; using getopts
does not require mandatory arguments prefixed by a flag letter. I tried to find a suitable example from my corpus of scripts; this is a semi-decent approximation. It is called rcsunco
and is used to cancel a checkout from RCS. I haven't modified it in a while, I see; I use it quite often (because I haven't migrated from RCS completely, yet).
#!/bin/sh
#
# "@(#)$Id: rcsunco.sh,v 2.1 2002/08/03 07:41:00 jleffler Exp $"
#
# Cancel RCS checkout
# -V print version number
# -n do not remove or rename checked out file (like SCCS unget) (default)
# -r remove checked out file (default)
# -k keep checked out file as $file.keep
# -g checkout (unlocked) file after clean-up
# -q quiet checkout
: ${RCS:=rcs}
: ${CO:=co}
remove=yes
keep=no
get=no
quiet=
while getopts gknqrV opt
do
case $opt in
V) echo "`basename $0 .sh`: RCSUNCO Version $Revision: 2.1 $ ($Date: 2002/08/03 07:41:00 $)" |
rcsmunger
exit 0;;
g) get=yes;;
k) keep=yes;;
n) remove=no;;
q) quiet=-q;;
r) remove=yes;;
*) echo "Usage: `basename $0 .sh` [-{n|g}][-{r|k}] file [...]" 1>&2
exit 1;;
esac
done
shift $(($OPTIND-1))
for file in $*
do
rfile=$(rfile $file)
xfile=$(xfile $rfile)
if $RCS -u $rfile
then
if [ $keep = yes ]
then
if [ -f $xfile ]
then
mv $xfile $xfile.keep
echo "$xfile saved in $xfile.keep"
fi
elif [ $remove = yes ]
then rm -f $xfile
fi
if [ $get = yes ] && [ $remove = yes -o $keep = yes ]
then $CO $quiet $rfile
fi
fi
done
It's only a semi-decent approximation; the script quietly does nothing if you don't supply any file names after the optional arguments. However, if you need to, you can check that the mandatory arguments are present after the 'shift'. Another script of mine does have mandatory arguments. It contains:
...
shift $(($OPTIND - 1))
case $# in
2) case $1 in
install) MODE=Installation;;
uninstall) MODE=Uninstallation;;
*) usage;;
esac;;
*) usage;;
esac
So, that command (jlss
) can take optional arguments such as -d $HOME
, but requires either install
or uninstall
followed by the name of something to install. The basic mode of use is:
jlss install program
But the optional mode is:
jlss -d $HOME -u me -g mine -x -p install program
I didn't show all of jlss
because it has about 12 options - it isn't as compact as rcsunco
.
If you were dealing with mandatory arguments before option arguments, then you'd have to do a bit more work:
If you are dealing with mandatory arguments interspersed with option arguments (both before and after the mandatory ones), then you have still more work to do. This is used by many VCS systems; CVS and GIT both have the facility:
git -global option command [-sub option] [...]
Here, you run one getopts
loop to get the global options; pick up the mandatory arguments; and run a second getopts
loop to get the sub-options (and maybe run a final loop over the 'file name' arguments).
Isn't life fun?
If you are using getops, just shift by $OPTIND-1
after your case statement. Then what is left in $*
will be everything else, which is probably what you want.
shift $(( ${OPTIND} - 1 )); echo "${*}"
Don't use the getopts builtin, use getopt(1) instead. They are (subtly) different and do different things well. For you scenario you could do this:
#!/bin/bash
eval set -- $(getopt -n $0 -o "-rvxl:" -- "$@")
declare r v x l
declare -a files
while [ $# -gt 0 ] ; do
case "$1" in
-r) r=1 ; shift ;;
-v) v=1 ; shift ;;
-x) x=1 ; shift ;;
-l) shift ; l="$1" ; shift ;;
--) shift ;;
-*) echo "bad option '$1'" ; exit 1 ;;
*) files=("${files[@]}" "$1") ; shift ;;
esac
done
if [ ${#files} -eq 0 ] ; then
echo output file required
exit 1
fi
[ ! -z "$r" ] && echo "r on"
[ ! -z "$v" ] && echo "v on"
[ ! -z "$x" ] && echo "x on"
[ ! -z "$l" ] && echo "l == $l"
echo "output file(s): ${files[@]}"
EDIT: for completeness I have provided an example of handling an option requiring an argument.