Bash: parsing arguments with ‘getopts’
Today I was writing some scripts, and in every script I wanted something to handle all input arguments, in a good way, so I could pass my arguments in any order and my program would know about it.
I used ‘getopts’ before, but this time I decided to write some stuff here about it.
Let me show you how useful it can be:
Let’s suppose that I’m writing a test script, that needs, as argument, the type of the test, the server, the server root password and for debugging purpose we’re going to have a verbose flag too. So, putting it down:
- “-t” – the type of the test, let’s suppose we have “test1″ and “test2″
- “-s” – the server
- “-p” – the root password of the server
- “-v”- a flag just to let the script run in a verbose mode
Ok, now how we’re going to write this script and parse these arguments? We can use the harder way, fixing an order and parsing it by hand at the script, something like this:
salveti@evalap /tmp/scripts $ cat test_script.sh
#!/bin/bash
# Argument order = -t test -r server -p password -v
TEST=$2
SERVER=$4
PASSWD=$6
if [[ $# -gt 6 ]]
then
VERBOSE=1
else
VERBOSE=2
fi
Alright, this works, but if you want to run the script with the arguments in a different way? Or if you forget and put it in the right order? It’ll not work, so, this is an ugly solution.
Ok, but how can you deal with arguments not worrying about the order and if needs an argument or not? Getopts is the answer
Let’s see how we can write the script using getopts and them we explain how it works.
The new script (it’s bigger, I’ll explain why):
#!/bin/bash
# Argument = -t test -r server -p password -v
usage()
{
cat << EOF
usage: $0 options
This script run the test1 or test2 over a machine.
OPTIONS:
-h Show this message
-t Test type, can be ‘test1′ or ‘test2′
-r Server address
-p Server root password
-v Verbose
EOF
}
TEST=
SERVER=
PASSWD=
VERBOSE=
while getopts “ht:r:p:v” OPTION
do
case $OPTION in
h)
usage
exit 1
;;
t)
TEST=$OPTARG
;;
r)
SERVER=$OPTARG
;;
p)
PASSWD=$OPTARG
;;
v)
VERBOSE=1
;;
?)
usage
exit
;;
esac
done
if [[ -z $TEST ]] || [[ -z $SERVER ]] || [[ -z $PASSWD ]]
then
usage
exit 1
fi
In this script I created a usage function, just to help you explaining all arguments.
Then, we can see the getopts' call while getopts "ht:r:p:v" OPTION, this is the main point of the script, it's how we deal with arguments using getopts. Getopts require an optstring and a var name, just to help you checking the arguments.
When you call getopts, it will walk in your optstring argument, identifying which argument needs a value and which don't. After getting an argument, getopts set the OPTION var, so you can check it using a case code block, or something like that. If your argument needs a value, getopts will set the var $OPTARG with the value, so you can check and see if it's what you were expecting (in this example, check if the test argument is passed with "test1" or "test2"). Easy hã?
Ok, but what is this ":" doing in the arguments? And why the arguments "h" and "t" are together?
This is an import point of getopts. You can use ":" in two cases, one when you want getopts to deal with argument's errors, and another to tell getopts which argument needs a value.
First, the error checking. When you pass the arguments to getopts in the optstring, getopts will only check what's there, so if you pass an argument that's not listed at optstring getopts will give an error (because it's not a valid argument). When you put ":" at the beginning of the optstring, ":ht:r:p:v" for example, getopts sets the OPTION var with "?" and the $OPTARG with the wrong character, but no output will be written to standard error; otherwise, the shell variable $OPTARG will be unset and a diagnostic message will be written to standard error (./test_script.sh: illegal option -- l, if you pass the argument -l, for example).
Second, how to tell getopts which argument needs a value. When you need an argument that needs a value, "-t test1" for example, you put the ":" right after the argument in the optstring. If your var is just a flag, withou any additional argument, just leave the var, without the ":" following it.
So, in the example, you can see that I'm leaving the error checking to getopts, the vars "t", "r", "p" needs a value and "v" is just a flag.
To finish the script, we have a var checking, just to see if all vars that needs a value are not empty.
And, that's it. For now, you can try making a new script and playing with it a little, it's not so hard and can help you very much when writing new scripts

Great example. Helped a lot to me. Thank you.
Very nice tutorial.
I dont like the fact that with getopts your parameter names can only be 1 character long though
btw if you do your tests like this : if [ -z "$TEST" ]
then you dont need to initialize your variables to empty values.
I was looking for a good getopts page, this is very concise and well written.
As Dieter_be also said, do you know of a way I can do long options too? Like:
./scriptname –help
Any help would be greatly appreciated.
Thank you.
–Andruk
4 different articles on getopts + bash man pages. didn´t understand it.
just skimmed you article and got it first time around. GREAT
Thanks a lot!
Apparently you cannot do long options with getopts. You can however with getopt.
getopts is a bash builtin that wraps around getopt. More info here : http://aplawrence.com/Unix/getopts.html
how to get two paras from commond line? for example, “# Argument = -t test -r server -p username password -v”. for -p, how to get username first and then password? thank you.
I don’t think that you can do this with getopts. Why don’t you just add another option for the password?
same situation as terry. Thats becoz that option, which is -p in terry’s case, needs two paras, if add another option for the passwd, that’d become two different options with two different paras, am i right?
Yes, you’d probably have one param for the username and another for the password.
Let’s say, you have an arbitrary number of hosts as parameters of an option – like: “myscript.sh -h host1 host2 host3 …”. No chance for getopts?
@Juergen: getopts will get, by default, only the first host. What you could do is give all the hosts in one string, and then parse it at your code (“host1,host2,host3″ for example). It’s not a good solution, but depending on what you have could be an easy solution, at least
I do not think it is not a good solution, just use space as divider and handle them in a for loop.
for host in $OPTARG; do …
As it is pointed out at http://aplawrence.com/Unix/getopts.html,
shift $((OPTIND-1)); OPTIND=1
after parsing an option (discards parameter, resets option parsing index) leaves you with just the remaining non-option arguments in $*
example: http://michaxm.de/~misc/prefix
I’d like to know how to stop getopts using one of the other options as an argument to an option which needs an argument.
e.g if you do test_script.sh -t -r it puts -r as $OPTARG for the -t option.
Any ideas?
@Anonymous: the trick is the ‘:’ after the option you want.
“You can use “:” in two cases, one when you want getopts to deal with argument’s errors, and another to tell getopts which argument needs a value.”
Thanks! That came in handy
Thank you for your post. It helped me today with my bash script that seemed to follow your example very closely.
Thanks, very useful.
I have @Anonymous’s problem because if I have only one
compulsory optarg option -t (“ht:v”) I cannot get around that:
test_script.sh -t -h
sets TEST to “-h” but provides no usage/error message.
Alternatively, using your script try the silly test:
test_script.sh -t -h -r -v -p -h
which generates TEST, PASWORD, and SERVER set, but no
usage/error message.
@Witsend: don’t think getopts have anything to help you solving this problem
What you could do is to check the opt values before assuming they are correct.
As I’ve discovered … and thanks.
I used ‘if [[ "$OPTARG" =~ "regexp" ]] …’
because my OPTARG was a regular expression,
but this may not work for all folk.
Whoops. Just noted bash 3.2 advice to leave
off the quotes around regexp in the previous
message. The reason is arcane … see
https://bugs.launchpad.net/ubuntu-website/+bug/109931
Hi Rsalveti. very well presented explaination. thanks a million as I have been going through books and website to get this clarified.
I have written a similar script as a test and I have a strange problem.
ie. ./myscript -a test1 -b test2
these parameters are acknowledged first time around but not thereafter.
until I run the script without parameters. then the next time I use the parameters they are acknowlwdged.
has anyone seen this problem in their scripts?
Thanks! Just what I needed. I wish I could understand the documentation for getopt so I could use long arguments, but this will do nicely.
One note: the case statement matchers are just regular expressions, so if you want two options to have the same result (like h and ?) you can just do:
h|\?)
usage
exit 1
;;
Hey, quit reducing the contrast (using gray, instead of white, on black). Don’t make the text harder to read.
A small step for most, a giant leap for me. My shell script abilities made a quantum jump… (from 0 to 1 I’m afraid
tank you VERY MUCH,
just in case, also:
http://www.linuxcommand.org/wss0130.php (useful for long options)
What if i want to have three arguements to any particular option
like -a can have only three
-a foo goo bar //how can i do this
mail me the answer if possible
rishi.b.agrawal@gmail.com
how about
myfunction -s 10 -a “foo goo bar”
then split the foo, goo and bar afterwards?
Thanks for this clear and succinct posting. It certainly helped me out.
You rock! Thanks for posting this. I was quickly up and runing using your instructions.
One thing I might clarify for others is that you need to put the colon after the switch letter in the optstring for any argument that needs a value…not just those that require the argument to be passed. I thought you meant the latter and omitted the colon for arguments that I wanted to be treated as option rather than required. That didn’t work. They’re made optional by defaulting them to something and not throwing an error if they’re not passed or if they’re blank., rather than by omitting the colon. The colon is necessary for any argument that is not just a flag.
What can I say, but thank you!