Take the 2-minute tour ×

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 script which receives arguments in this form:

script.sh [optional arguments] [anything required]

for example

script.sh -rvx output_file.txt

where the script says you HAVE to have an output file. Is there any easy way to do this?

As far as I know, with getopts it would have to look like: script.sh -rvx -f output_file.txt, and that is just not very clean.

I can also use python if necessary, but only have 2.4 available, which is a bit dated.

share|improve this question

5 Answers 5

up vote 26 down vote accepted

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:


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 ;;

if [ ${#files} -eq 0 ] ; then
        echo output file required
        exit 1

[ ! -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.

share|improve this answer
This looks like it does exactly what I want. One question with this, however: if I want to allow a flag that takes an argument (for example, -l file.txt), what would be the syntax? Thanks again. –  A Question Asker Jan 3 '11 at 18:39
Consult the getopt man page for that. Briefly: append a colon to the option (e.g. "-rv:x", which requires v to take an argument). Long options are also supported, which is one way it trumps the getopts builtin. Note that you will have to handle the additional non-option argument yourself. –  Sorpigal Jan 3 '11 at 18:43
Thank you so much. An excellent response. –  A Question Asker Jan 3 '11 at 18:45
@A Question Asker: Be aware that there are issues with some versions of getopt. From the man page: "Traditional implementations of getopt(1) are unable to cope with white space and other (shell-specific) special characters in arguments and non-option parameters." See the man page for more details and a way to test whether your version has these issues or not. –  Dennis Williamson Jan 3 '11 at 18:57

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 "${*}"
share|improve this answer
+1 you beat me to it. This is the correct answer. –  Dennis Williamson Jan 3 '11 at 19:01
Yup. Just check $# to make sure the right number of required arguments were passed, then access them as $1, $2, etc as usual. –  Gordon Davisson Jan 3 '11 at 19:09

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).

#   "@(#)$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}


while getopts gknqrV opt
    case $opt in
    V)  echo "`basename $0 .sh`: RCSUNCO Version $Revision: 2.1 $ ($Date: 2002/08/03 07:41:00 $)" |
        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;;

shift $(($OPTIND-1))

for file in $*
    rfile=$(rfile $file)
    xfile=$(xfile $rfile)
    if $RCS -u $rfile
        if [ $keep = yes ]
            if [ -f $xfile ]
                mv $xfile $xfile.keep
                echo "$xfile saved in $xfile.keep"
        elif [ $remove = yes ]
        then rm -f $xfile
        if [ $get = yes ] && [ $remove = yes -o $keep = yes ]
        then $CO $quiet $rfile

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;;
*)  usage;;

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:

  • You'd pick up the mandatory arguments, shifting them out of the way.
  • Then you process the optional arguments with the flags.
  • Finally, if appropriate, you handle the extra 'file name' arguments.

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?

share|improve this answer

And I heard a completely opposite thing, that you shouldn't use getopt, but the getopts builtin.

Cross-platform getopt for a shell script


The POSIX shell (and others) offer getopts which is safe to use instead.

share|improve this answer

Here's yet another way to "Option-ize your shell scripts" (whithout using getopt or getopts):


share|improve this answer

Your Answer


