How to easily create bash scripts that support options

Leverage the built-in `getopt` shell routine to create easy to use and extend Bash scripts that support options like many Linux command line utilities.

How to easily create bash scripts that support options

Sample script

Lets say we want to create a bash script called archive-project.sh to archive a Gitlab project repository. Here is how we want the script to be usable :

(bash)$ ./archive-project.sh -h

Archive Gitlab project repository

Usage :
> ./archive-project.sh --id <project-id> -u <gitlab-url> --token <gitlab-token>

Options :

-h, --help               Show help on how to use this script
--id                     ID of the project to archive
-u, --url                URL of the Gitlab instance where the project resides
-t, --token              User API token to use for authentication. 
                         The user  generating the API token need to have
                         'Owner' privilege on the project to archive

As you can see, the script users will be able to set options to tell the script which project to archive, on which Gitlab instance, using which authentication token. getopt can be very useful in achiving that. We will see how, keep reading 😉

Benefits of using getopt

The getopt shell routine can be very useful to create shell scripts that support options. getopt will take care of the following for you :

  • check and validate your predefined options
  • recognize your short (-s for instance), long (--my-long-option) or alternate (-myoption) options
  • check and validate options arguments when required
  • exit with error code different from 0 when something goes wrong (required option argument not set, undefined options passed to the script...)
  • print precise error messages (in your locales configured language)
  • ability to disable error message printing in order to print your custom messages

How getopt works ?

When using getopt, two parts separated by -- can be identified. The first part is where we customize getopt behavior by using some of its options to tell which predefined options we want our scripts to support, on which form we want them to be (long: --something, short: -b ...), do they have required parameters, and so on.

The second part is the script arguments array that getopt will parse to recognize our predefined options and do some treatments. That second part is generally retrieved inside bash scripts with "$@" variable, which stores the bash script arguments.

Here is the getopt command line to use inside the archive-project.sh sample script :

getopt -s 'bash' -o 'h,u,t' -a -l 'help,id:,url:,token:' -n 'archive-project.sh' -- "$@"

In the above getopt command, we used the following options :

  • --name, -n : the name of the script that will be used by getopt when reporting some errors... for instance, when detecting unrecognized options in bash scripts arguments.
    Ex : archive-project.sh : unrecognize option '--fakeoption'
  • --shell, -s : represents the type of shell we will use getopt in for parsing scripts arguments... can be bash or tcsh
  • --longoptions, -l : tell getopt to recognize the comma separated long options we specified... the : at the end of each option tell getopt that arguments are required for those options. We can also use :: instead to tell getopt that we want argument to be optional for a specific options.
  • -a, --alternative : allow long options to start with a single -. This means that using -token for instance when using the script will also work in addition to --token. When parsing the script args, you have to use the long options form (--token) in the while/case loop... -token won't be recognize...
  • -o, --options : short options

We can also use -q option in the preceding getopt command line if we don't want to print built-in error messages. Not using the -q option makes getopt prints error messages when users :

  • call the script with an undefined option
  • call the script with a defined option but whitout argument, when one is required
  • for an error related to getopt itself

Example error messages

(bash)$ ./archive-project.sh --id
archive-project.sh: option '--id' requires an argument

(bash)$ ./archive-project.sh --non-existent-option
archive-project.sh: unrecognized option '--non-existent-option'

Using getopt to implement the sample script

In this section we will demonstrate how to implement the behavior defined for the archive-project.sh script and see in practice, how user friendly scripts that are easily configurable and extendable could be built by leveraging getopt features.

As a reminder, here is how we want the script to be usable :

(bash)$ ./archive-project.sh -h

Archive Gitlab project repository

Usage :
> ./archive-project.sh -id <project-id> -u <gitlab-url> --token <access-token>

Options :

-h, --help               Show help on how to use this script
-id, --project-id        ID of the project to archive
-u, --gitlab-url         URL of the Gitlab instance where the project resides
-t, --access-token       User API token to use for authentication. 
                         The user  generating the API token need to have
                         'Owner' privilege on the project to archive

As you can see, it is easily configurable thanks to options. Adding new features simply means new options for users, which is very user friendly.

In the first part of the script we define functions :

#!/bin/bash

# Print usage
usage() {
  echo -e "\nArchive Gitlab project repository \n"
  echo -e "Usage : $0 -id <project-id> -u <gitlab-url> --token <access-token> \n"
  echo -e "Options : \n"
  echo -e "-h, --help               \t Show help on how to use this script"
  echo -e "-id, --project-id        \t ID of the project to archive"
  (...)
  exit 1
}

# Archive project
archive_project()
{
  gitlab_url=$1    # value taken from the first argument of the function
  project_id=$2    # value taken from the second argument of the function
  access_token=$3  # value taken from the third argument of the function
  
  # code to archive Gitlab project here
}

The usage function will be called when the script is run whithout arguments or with the -h option. The archive_project function will be called when users properly set each of the options required to archive a Gitlab project.

Here is the remaining part of the archive-project.sh script devided in multiple pieces :

Piece #1

# getopt command used to add option support
GETOPT=$(getopt -s 'bash' -o 'h,u:,t:' -a -l 'help,id:,url:,token:' -n 'archive-project.sh' -- "$@")

# If there is any error with the previous getopt command,
# an explicit error message will be printed by getopt and
# the script will exit
if [ $? -ne 0 ]; then
    exit 1
fi

# If no argument is provided print usage and exit
if [[ -z "$@" ]] ; then usage ; exit 1 ; fi

Piece #2

eval set -- "$GETOPT"
  • Sets the value of positional arguments ($@) to the value of GETOPT variable
  • This is done only when the getopt command in Piece #1 succeeds
  • The GETOPT variable will simply contain all arguments passed to the script plus the -- at the end. Example :
# Script execution command
(bash)# ./archive-project.sh -id 152 --gitlab-url https://gitlab.hackerstack.org --access-token mytoken

# GETOPT variable resulting content
-id '152' --gitlab-url 'https://gitlab.hackerstack.org' --access-token 'mytoken

Piece #3

while true; do
    case "$1" in
        -h|--help)
            usage
        ;;
        --id)
            id=$2
            shift 2;
        ;;
        -u|--gitlab-url)
            url=$2
            shift 2;
        ;;
        -t|--access-token)
            token=$2
            shift 2;
        ;;
        --)
            shift ;
            break ;
        ;;
    esac
done

# Exploit parsed options arguments to archive the Gitlab project
archive_project $url $id $token
  • Retrives options arguments from positional arguments (contained in $@) thanks to a while/case loop.
  • For each valid option present in the script arguments, do something. For the -h or --help options for instance, call the usage function
  • For each of the others options, set their arguments values to a new variable and move to the next option in the positional argument (shift 2)
  • We are at the end of the positional arguments when the -- argument is retrieved. It's time to exit the loop (shift + break) and exploit the saved options values
  • The users inputs required to archive a Gitlab project, passed through the corresponding options are now saved in the url, id and token variables and can be passed to the archive_project function to archive the corresponding project

And voilà ! Now you know how to build bash utilities that are user friendly, configurable and easily extendable thanks to flags support added with getopt