ESN 78255-080107-699684-48


Document Name: Getopt and getopts
Document Description: ./Unix/getopts.html

Getopt and getopts

Both "getopt" and getopts are tools to use for processing and validating shell script arguments. They are similar, but not identical. More confusingly, functionality may vary from place to place, so you need to read the man pages carefully if your usage is more than casual.

Properly handling command line arguments is difficult if you want the usage to be flexible. It's easy to write a script that demands arguments in a specific order; much harder to allow any order at all. It's also hard to allow bunched together arguments or spaced out to be equivalent:

 foo -a -b -c
 foo -abc
 foo -a -c +b
 foo -ac +b
 

These are the problems that getopt(s) are designed to handle. They ease the job considerably, but introduce their own little quirks that your script will need to deal with.

getopt

This is a standalone executable that has been around a long time. Older versions lack the ability to handle quoted arguments (foo a "this won't work" c) and the versions that can do so clumsily. If you are running a recent Linux version, your "getopt" can do that; SCO OSR5, Mac OS X 10.2.6 and FreeBSD 4.4 has an older version that does not.

The simple use of "getopt" is shown in this mini-script:

 #!/bin/bash
 echo "Before getopt"
 for i
 do
   echo $i
 done
 args=`getopt abc:d $*`
 set -- $args
 echo "After getopt"
 for i
 do
   echo "-->$i"
 done
 

What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).

If we call this "g" and try it out:

 bash-2.05a$ ./g -abc foo
 Before getopt
 -abc
 foo
 After getopt
 -->-a
 -->-b
 -->-c
 -->foo
 -->--
 

We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".

Of course "getopt" doesn't care that we didn't use "-d"; if that were important, it would be up to your script to notice and complain. However, "getopt" will notice if we try to use a flag that wasn't specified:

 bash-2.05a$ ./g -abc foo -d -f
 Before getopt
 -abc
 foo
 -d
 -f
 getopt: illegal option -- f
 After getopt
 -->-a
 -->-b
 -->-c
 -->foo
 -->-d
 -->--
 

However, if you preface the option string with a colon:

 args=`getopt :abc:d $*`
 
"getopt" will be silent about the unwanted flag.

As noted at the beginning, if we give "getopt" arguments containing spaces, it breaks:

 bash-2.05a$ ./g -abc "foo bar"
 Before getopt
 -abc
 foo bar
 After getopt
 -->-a
 -->-b
 -->-c
 -->foo
 -->--
 -->bar
 

Not only has "foo bar" become two arguments, but they have been separated. This will be true whether you have the newer version that is capable of handling those arguments or not, because it requires different syntax to handle them. If you do have the newer "getopt", you'd need to write the script differently:

 #!/bin/bash
 echo "Before getopt"
 for i
 do
   echo $i
 done
 args=`getopt -o abc: -- "$@"`
 eval set -- "$args"
 echo "After getopt"
 for i
 do
   echo "-->$i"
 done
 

We've added a "-o", changed $* to $@ in quotes, and used an "eval" for the set. With the newer (as is on Linux) version, that works:

 bash-2.05a$ ./g -abc "foo bar"
 bash-2.05a$ ./g -abc "foo bar"
 Before getopt
 -abc
 foo bar
 After getopt
 -->-a
 -->-b
 -->-c
 -->foo bar
 -->--
 

However, if you use that script with the older getopt, you get a useless result:

 bash-2.05a$ ./gg -abc "foo bar"
 Before getopt
 -abc
 foo bar
 After getopt
 -->--
 -->abc:
 -->--
 -->-abc
 -->foo
 -->bar
 

It's unfortunately easy to get bad results from "getopt" by misquoting or using the wrong syntax. Whenever I've had to use this, I make sure to print out the arguments as I did in the "After getopt" while testing. Once you get it right, using it is easy:

 #!/bin/bash
 # (old version)
 args=`getopt abc: $*`
 if test $? != 0
      then
          echo 'Usage: -a -b -c file'
          exit 1
 fi
 set -- $args
 for i
 do
   case "$i" in
         -c) shift;echo "flag c set to $1";shift;;
         -a) shift;echo "flag a set";;
         -b) shift;echo "flag b set";;
   esac
 done
 

and the results are as expected.

 bash-2.05a$ ./g -abc "foo"
 flag a set
 flag b set
 flag c set to foo
 bash-2.05a$ 
 

However, note the "Usage" section which prints if "getopt" doesn't like what you gave it: an extra flag, or not giving an argument to a flag that requires one. Using the this newest script, we can test some of that:

 bash-2.05a$ ./g  -ab  -c 
 getopt: option requires an argument -- c
 Usage: -a -b -c file
 Bash-2.05a$ ./g  -abj foo
 getopt: illegal option -- j
 Usage: -a -b -c file
 

But "getopt" is easily fooled:

 bash-2.05a$ ./g  -a -c -b foo 
 flag a set
 flag c set to -b
 flag b set
 

You'd have to deal with that nastiness yourself.

getopts

sh and bash builtin. Easier to use and generally better than getopt, though of course not available in csh-like shells. You shouldn't be using those anyway.

This works rather differently than "getopt". First, because it's a built-in, you usually won't find a separate man page for it, though "help getopts" may give you what you need.

The old "getopt" is called once, and it modifies the environment as we saw above. The builtin "getopts" is called each time you want to process an argument, and it doesn't change the original arguments . A simple script to test with:

 #!/bin/bash
 while getopts  "abc:" flag
 do
   echo "$flag" $OPTIND $OPTARG
 done
 

Trying this produces good results:

 bash-2.05a$ ./g -abc "foo"
 a 1
 b 1
 c 3 foo
 

The "$OPTIND" will contain the index of the argument that will be examined next. If you really needed to, you could tell from that whether arguments were bunched together or given separately, but the real point of it is to let you reset it to re-process the arguments. Try this slightly more complicated version (we'll call it "gg"):

 #!/bin/bash
 while getopts  "abc:def:ghi" flag
 do
   echo "$flag" $OPTIND $OPTARG
 done
 echo "Resetting"
 OPTIND=1
 while getopts  "abc:def:ghi" flag
 do
   echo "$flag" $OPTIND $OPTARG
 done
 

We'll give it more arguments so that you can observe it at work:

 bash-2.05a$ ./gg -a -bc foo -f "foo bar" -h -gde
 a 2
 b 2
 c 4 foo
 f 6 foo bar
 h 7
 g 7
 d 7
 e 8
 Resetting
 a 2
 b 2
 c 4 foo
 f 6 foo bar
 h 7
 g 7
 d 7
 e 8
 

The leading ":" works like it does in "getopt" to suppress errors, but "getopt" gives you more help. Back to our first simple version:

 sh-2.05a$ ./g  -a -c -b foo
 a 2 
 c 4 -b
 

The builtin "getopts" doesn't get fooled: the "-b" is the argument to c, but it doesn't think that b is set also.

If "getopts" encounters an unwanted argument, and hasn't been silenced by a leading ":", the "$flag" in our script above will be set to "?":

 bash-2.05a$ ./g  -a -c foo  -l 
 a 2 
 c 4 foo
 ./g: illegal option -- l
 ? 4 
 bash-2.05a$ ./g  -a -c         
 a 2 
 ./g: option requires an argument -- c
 ? 3 
 

With a leading ":" (while getopts ":abc:d" flag), things are different:

 bash-2.05a$ ./g  -a -c 
 a 2
 : 3 c
 bash-2.05a$ ./g  -a -c foo  -l
 a 2
 c 4 foo
 ? 4 l
 bash-2.05a$ ./g  -a -c
 a 2
 : 3 c
 

If an argument is not given for a flag that needs one, "$flag" gets set to ":" and OPTARG has the misused flag. If an unknown argument is given, a "?" is put in "$flag", and OPTARG again has the unrecognized flag.

See also Perl Getopts

© September 2003 Tony Lawrence All rights reserved

Author: Anthony Lawrence - Contact Author
Publisher: Anthony Lawrence
Licensee Name: Anthony Lawrence
Reference URL: http://aplawrence.com/Unix/getopts.html
Copyright: All Rights Reserved
Registration Date: 1/7/2008 9:44:02 PM UTC
Views: 110011




NUMLY.COM