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.

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 options

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 cannot be any spaces between the option (-a) and the argument (bahman).

Long options

A long option is composed of a -- (two consecutive 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

  • Each single character stands for an option.
  • A : (colon character) tells that the option has a required argument.
  • A :: (two consecutive 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

  • Options are separated by , (comma character).
  • A : (colon character) tells that the option has a required argument.
  • A :: (two consecutive colon characters) 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.
#!/usr/bin/env 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

Comments

  1. I did quite a bit of searching for introductions to getopt, and everything I found was packed with waaay too much and advanced information without even explaining things (why do programmers tend to with with so much da** complication and not even explain things??) until I found this, which was simple, to the point and actually explained things. Thank you.

    ReplyDelete
  2. Line 29 with the "Internal Error!" message doesn't work. No matter what wrong option you write, the message doesn't appear. Only the "invalid option --" lines appear. This is a problem I face also with my getopt script.

    ReplyDelete
    Replies
    1. That's expected 😄 Any "invalid" option will be caught be getopts on line #11 before it reaches line #29.

      The usecase for line #29 is when the options on line #11 are more general than your program may accept (for backward compatibility for example.) Then you would reject the unsupported options on line #29.

      If you want to silence the "invalid option" error, add `--quite` to getopts options on line #11.

      Hope this helps.

      Delete
    2. Thank you for your explanation, sir, but what I want in my script is that when it detects that the user has entered an invalid option like "-o", the program ends at that moment without showing anything else except the error message.
      Sorry for bothering you with a question about a script from more than 6 years ago. The thing is that achieving the result I'm talking about has kept me obsessed.
      And sorry for my bad english It's not my first language.

      Delete
    3. I'm afraid that's not something you can achieve with the snippet I provided.

      That said, you *could* try redirecting getopts stderr and exit the program if the exit code is not 0. I haven't tried it but on paper it may work.

      Delete

Post a Comment

Popular posts from this blog

Variables in GNU Make: Simple and Recursive

Checkmate on Your Terms: A Personal Journey with Correspondence Chess

Firefox profiles: Quickly replicate your settings to any machine