You are here: Home / Blogs / Command Line Options: How To Parse In Bash Using “getopt”

Command Line Options: How To Parse In Bash Using “getopt”

Use “getopt” in a Bash script to parse long and short command line options, enforce arguments for some options and spot dubious user input.

Introduction

Most of the times, when you need to automate system administration tasks on your Linux machine, writing a Bash script is your best bet. And sometimes, you need to be able to control the behaviour of your script at some point which leaves you with two choices: use environment variables (set before running the script) as sort of a flag or the better and more intuitive way to use command line arguments.

 

What is “getopt”?

getopt is a program that parses command line options in shell scripts. It is the enhanced version of older getopts and uses the getopt C library to do its job. It is compatible with getopts as long as GETOPT_COMPATIBLE environment variable is set, however some of it best features are not available in compatibility mode.

 

An overview of command options

Command line input is viewed in 3 categories by getopt: short options (like -a), long options (like --some-option) and non-option parameters (like /home/bahman/reports.txt). Also short and long options can accept arguments (like -a /home/bahman/Temp or --some-option 'A commit comment').

Short option

A short option is composed of a - (dash character) followed by a single letter, for example -a or -A, and it may expect an argument.

  • Without argument like -a or -H
  • With argument
    • With required arguments like -a bahman or -Hreports
    • With optional arguments like -abahman. Note that there can't be any spaces between the option (-a) and the argument (bahman).

Long option

A long option is composed of a -- (two consequent dash characters) followed by any number of alpha-numeric characters (like --option-a) and it may expect an argument.

  • Without argument like --option-a or --more-2-go
  • With arguments
    • With required arguments like --file-to-process reports or --package-name-prefix='com.bahmanm'
    • With optional arguments like --package-name-prefix='com.bahmanm'. Note that the argument can be passed only using =.

 

What is an “option string”?

The only way to tell getopt about the options it should expect is by building an option string. Normally you would pass 2 option strings to getopt, one for short options and the other for long options.

Option string for short options

It is passed to getopt using -o option and follows the rules below.

Rules:

  1. Each single character stands for an option.
  2. A : (colon character) tells that the option has a required argument.
  3. A :: (two consequent colon character) tells that the option has an optional argument.

Example:

The option string f:gh::i: means that the are four options. f has a required argument, g has no argument, h has an optional argument and i has a required argument.

Option string for long options

It is passed to getopt using --long option and follows the rules below.

Rules:

  1. Options are separated by , (comma character).
  2. A : (colon character) tells that the option has a required argument.
  3. A :: (two consequent colon character) tells that the option has an optional argument.

Example:

The options string foo,bar::,baz:,foobar means that there are four options. foo has no argument, bar has an optional argument, baz has a required argument and foobar has no argument.

 

Show me some real code!

Here's a sample Bash script parsing and dealing with options --I hope I've put enough comments in.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/bash

# “a” and “arga” have optional arguments with default values.
# “b” and “argb” have no arguments, acting as sort of a flag.
# “c” and “argc” have required arguments.

# set an initial value for the flag
ARG_B=0

# read the options
TEMP=`getopt -o a::bc: --long arga::,argb,argc: -n 'test.sh' -- "$@"`
eval set -- "$TEMP"

# extract options and their arguments into variables.
while true ; do
    case "$1" in
        -a|--arga)
            case "$2" in
                "") ARG_A='some default value' ; shift 2 ;;
                *) ARG_A=$2 ; shift 2 ;;
            esac ;;
        -b|--argb) ARG_B=1 ; shift ;;
        -c|--argc)
            case "$2" in
                "") shift 2 ;;
                *) ARG_C=$2 ; shift 2 ;;
            esac ;;
        --) shift ; break ;;
        *) echo "Internal error!" ; exit 1 ;;
    esac
done

# do something with the variables -- in this case the lamest possible one :-)
echo "ARG_A = $ARG_A"
echo "ARG_B = $ARG_B"
echo "ARG_C = $ARG_C"

 

Conclusion

You can use getopt to professionally deal with command options in a civilised manner in your Bash scripts. No need for dirty tricks.

Don't forget to read the getopt manpage. There's still some minor topics I haven't covered here, like testing for getopt's existence (-T).

Image source: dreamstime.com

Filed under: ,
Codiak says:
Mar 13, 2015 05:53 PM

Nice post, it was very helpful.
Thanks.

Algone says:
Mar 18, 2015 11:44 AM

Thank you for an helpful post. I was not getting the value of one of my options. After reading this piece I found out that I was actually doing "he:u:p:d" instead of "he:u:p:d:"

Freeda says:
Apr 20, 2015 01:47 PM

Hi.. I want to get the value of ARG_A when I pass the argument to the script. If i am passing "--arga 10" then it should print 10 as the answer in the end instead of "some default value", but that is not happening. Could you pls help me on this. Thanks..

Bahman Movaqar says:
Apr 21, 2015 02:17 AM

Freeda, read the "long options" sub-section again. When passing values to long options with optional arguments, you MUST use '='.
In your case, `--arga=10` does the trick.

gustav says:
Oct 29, 2015 10:49 AM

No it did not. nothing is happening. The loop does not work.

Michael Gower says:
Apr 20, 2015 06:42 PM

I had a similar problem with ARG_A. I fixed it by putting the following in the -a case statement:
shift;
if [ -n "$1" ]; then
ARG_A=$1;
elif [ -n "$3" ]; then
ARG_A=$3;
else
ARG_A="Some Default Value.";
fi
shift;
;;

Michael Gower says:
Apr 20, 2015 07:14 PM

My mistake. Change the TEST string to `getopt -o a:bc: --long arga:,argb,argc: -n 'test.sh' -- "$@"` (Notice that there is only one colon after 'a' and 'arga').

Bahman Movaqar says:
Apr 21, 2015 02:18 AM

Correct. That's the point. If you have a long option with an optional value (i.e. '::'), you have to pass the value using '='

Freeda says:
Apr 21, 2015 12:04 AM

Hi, I want the test string to be `getopt -o a::bc: --long arga::,argb,argc: -n 'test.sh' -- "$@"` where arga is an optional argument. If we don't pass any values to it, it will take the default value. In case if we pass, it should take the value the user has passed to it. It is not handling parsing optional arguments. Could you pls help me on this.
Ex:
If I pass the arguments to the above script like this:
sh test.sh --arga 10 --argc 20
print the arguments soon after accepting it:
--arga 10 --argc 20
print after getopt:
--arga 10 --argc 20
print after 'eval set -- "$TEMP"'
--arga --argc 20 -- 10
In this $2 for --arga has null string.

How to resolve this.

Bahman Movaqar says:
Apr 21, 2015 02:20 AM

Freeda, please see my reply above.

Freeda says:
Apr 21, 2015 05:08 AM

Thanks Bahman Movaqar. It resolved my issue.. :)

fujisan says:
May 07, 2015 08:45 AM

If I run this, i get:

./x.sh -ax -b -c
test.sh: option requires an argument -- 'c'
ARG_A = x
ARG_B = 1
ARG_C =

it complains that -c has no argument.

but if i run this:
./x.sh -ax -c -b
ARG_A = x
ARG_B = 0
ARG_C = -b

no complain because -b is considered as the argument of -c.

getopt cannot make the difference between a true argument and an option starting with '-'.

How can you fix that?

Bahman Movaqar says:
May 12, 2015 03:47 PM

@fujisan: Good question! I'll play with it this weekend and will update the solution.

sobicek says:
Aug 15, 2016 03:52 PM

I'm having the same problem with getopts (not getopt). Have you found a solution?

LudvikWoo says:
May 21, 2015 11:14 PM

Hi,I don't know why my shell can't be executed exactly. The "-o" will be change to "--" ,and the $1's value is "--". I try again and again ,but really can't find out the problem.

TEMP=`getopt -o "a::bc:" -l "a-long::,b-long,c-long:" -n 'getopt1.sh' -- "$@"`
echo "before set : $TEMP"
eval set -- "${TEMP}"
echo "after set : $TEMP"

result is :
before set : -- a::bc: -l a-long::,b-long,c-long: -n getopt1.sh -- -a111 -b -c 123
after set : -- a::bc: -l a-long::,b-long,c-long: -n getopt1.sh -- -a111 -b -c 123

LudvikWoo says:
May 21, 2015 11:20 PM

I try to change the code as same as your code ,but the result is the same :

TEMP=`getopt -o a::bc: --long arga::,argb,argc: -n 'getopt2.sh' -- "$@"`
echo $TEMP
eval set -- "$TEMP"
echo $TEMP

result is :
$./getopt2.sh -a 111 -b -c 123
-- a::bc: --long arga::,argb,argc: -n getopt2.sh -- -a 111 -b -c 123
-- a::bc: --long arga::,argb,argc: -n getopt2.sh -- -a 111 -b -c 123
ARG_A =
ARG_B = 0
ARG_C =

Bahman Movaqar says:
Jun 25, 2015 11:37 AM

@LudvikWoo I don't see any problem with your code. Are you sure you're running this on 'bash' and not on other shells?

YG says:
Jun 25, 2015 12:20 PM

Hi,

Could u please explain me the getopt command along with its last two arguments '-- "$@"' .
Also what does the statement "eval set -- $TEMP" do ?

Bahman Movaqar says:
Jun 26, 2015 12:29 PM

$@ means all positional parameters (see bash manpage "Special Parameters" section).
"eval set -- $TEMP" sets the results of 'getopt' into the current shell.

Kenneth Holter says:
Feb 18, 2016 05:34 AM

Works like a charm. Thank you!

Subash says:
Apr 26, 2016 07:29 AM

Nice explanation!

Nikhil Naole says:
May 24, 2016 07:33 AM

Very good tutorial !

Nikhil Naole says:
May 24, 2016 07:33 AM

Very good tutorial !

Bobby says:
Jul 21, 2016 08:09 PM

I read this post fully about the resemblance of most recent and preceding technologies, it's remarkable article.

Michelle says:
Jul 22, 2016 10:33 PM

Thankfulness to my father who informed me on the topic of this webpage, this website is really awesome.

Buster says:
Jul 24, 2016 11:57 AM

What's up colleagues, pleasant paragraph and fastidious urging commented at this place, I am genuinely enjoying by these.

Buster says:
Jul 24, 2016 11:58 AM

What's up colleagues, pleasant paragraph and fastidious urging commented at this place, I am genuinely enjoying by these.

borisn/ says:
Sep 12, 2016 11:29 AM

Please use /usr/bin/env bash in your hashbang instead of /bin/bash. /usr/bin/env is supposed to be universal, a standard. /bin/bash while common is not standard.

Keith says:
Mar 01, 2017 08:38 PM

I don't get it. How is this better than just using a while loop without using getopt? In programming languages, you have variables in which you can access the arguments. In your bash example, it just looks superfluous.

Rohit says:
Mar 17, 2017 06:37 AM

I want one option to be taken only once:
-a fun -b sun -a fun (i dont want like this).
how can i do this?

Add comment

You can add a comment by filling out the form below. Plain text formatting.

Question: What is 9 + 7 - 7 ?
Your answer: