Getopt and getopts

网友投稿 1336 2022-09-25

Getopt and getopts

Getopt and getopts

Getopt and getopts

"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:

Hate these ads?

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

Technorati tags:

Code

Programming

Comments /Unix/getopts.html

GetOpts :

Good article! It should also be noted that getopts is part of the Korn shell (ksh) shipped with SCO OSR5 and Unixware.

--BigDumbDinosaur

Ahh.. forgot about ksh! Thanks..

--TonyLawrence

Mon May 16 00:54:44 2005: Subject:

anonymous

Great! thank's a lot!

Tue Jun 7 15:17:15 2005: Subject:

anonymous

Of course, getopts doesn't handle long option names, while getopt does.

Tue Jun 7 18:03:59 2005: Subject:

BigDumbDinosaur

Of course, getopts doesn't handle long option names, while getopt does.

So don't use long option names. Besides, typing long option names gets annoying in a big hurry. If you need to specify more than 52 options (that 26 LC and 26 UC for those who are not familiar with UNIX shell case sensitivity), how about using a config file? Mon Jun 20 23:42:37 2005: Subject: Awesome! dannyman Okay, but after I loop through getopts, how do I reset the $@ stuff in bash so that I can read in subsequent arguments? For a thing like: ./foo.sh -a -b -c file.conf arg1 arg2 arg3 ... Wed Jun 22 20:02:16 2005: Subject: TonyLawrence By doing OPTIND=1 Fri Aug 5 22:51:11 2005: Subject: anonymous or maybe :

shift $((OPTIND-1))

Fri Sep 23 20:34:42 2005: Subject: getopt is bad

anonymous

(your comments go here)

While I understand it's never going away, getopt() is a really bad, bad, bad piece of code. This is the part of it in C I hate:

extern int optind;

extern char *optarg;

I detest functions like this that use hard coded externs that make programmers not older than dirt scratch their head and say , what the heck?

Fri Sep 23 22:36:23 2005: Subject:

BigDumbDinosaur

I detest functions like this that use hard coded externs that make programmers not older than dirt scratch their head and say, "what the heck?"

Whaddya mean "older than dirt?" I completely resemble that remark.

Seriously, library functions like getopt are part of the glue that holds a coherent UNIX API together. I have a big problem with some of the programming youngsters who, rather than understand and work with standard library functions that we old dinosaurs have used for decades, want to come up with something different just because they don't understand the existing function or the associated variable names (optind makes perfect sense to me -- it's option index). It's that sort of narrow thinking that has resulted in some of the language abortions we have today, such as Java and C++.

BTW, just how would you implement a library function like getopt and provide identical functionality, without any use of "hard coded externs"? Fri Oct 28 17:36:01 2005: Subject: Get the rest from getopts anonymous The comments above asked about getting the rest of the args from getopts and suggested setting OPTIND. I hacked out something, and I think it's a better way, something like this:

max=0

while getopts "eprvh" flag

do

case $flag in

... (omitted)

esac

if [ $OPTIND -gt $max ]

then

#echo "setting max to $OPTIND"

max=$OPTIND

fi

done

shift $max

# now $@ just has what's left

# note this only works if you program is called with options first like

# gg -evp arg1 arg2

# and not

# gg -ev arg1 -ph arg2

#

# Hope this helps - Ken

Thu Nov 17 14:21:40 2005: Subject:

anonymous

In your case statement, just useshift $((OPTIND-1)); OPTIND=1after you've processed a certain option (and it's argument).

bartX

Thu Dec 14 20:12:02 2006: Subject:

Dave

Found this very useful, thanks!

Thu Dec 14 23:45:42 2006: Subject:

BigDumbDinosaur

Naturally, all of this getopts maneuvering is thoroughly documented in a 1000 places (including here), and buried somewhere in that documentation (What!!!- I have to actually read this crap-) is the little part about usingshift $((OPTIND-1)); OPTIND=1to reset the option index to a new "ground zero" setting. Obviously, the anonymous person who pointed this out *has* read the documentation.

Wed Jan 23 16:59:10 2008: Subject:

anonymous

Thanks for this article -- it's a shame that the bash info file is so example free. I could have never figured out what was meant from that alone.

Fri Feb 8 06:56:52 2008: Subject:

anonymous

Great article, very useful! Thank you a lot!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Linux或Unix下怎样修改系统时间(更新版)
下一篇:全网营销该如何选择正确的策略?(有哪些常见的网络营销策略)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~