Passing meaningful command line arguments in shell scripts
Bash the #BASH

Passing meaningful command line arguments in shell scripts

I was trying to write this installer using shell script which would take command line arguments and setup an entire environment for my project i.e. EspoCRM and one of the problems I was facing here was; I wanted to pass the version of EspoCRM to download through command line. Initially, this is how I would have passed the arguments

./installer "5.4.3" "1" "1"

Doesn't make much sense, does it?

The above command arguments means do the following:

  1. "5.4.3" means Download EspoCRM version 5.4.3 if not already downloaded
  2. "1" means run it in verbose mode
  3. "1" means rebuild the project

I would have made much more sense, if I could just specify this

./installer -rebuild -verbose -version='5.4.3'

After doing some research, I found out that getopts was builtin command used by bash to parse command line named arguments. However, getopts could only parse the single character arguments.

Personally, I was not satisfied with just parsing single character because it still would have cause confusions in this type of commands

./installer -v "5.4.3" -V -r

Here,

  • -v means version
  • -V means verbose
  • -r means rebuild

So, I continued my search for better option parsers. That's when I found getopt.

Why getopt?

To parse elaborated command-line arguments to avoid confusion and clarify the options we are parsing so that reader of the commands can understand what's happening.

What is getopt?

`getopt` is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options. It uses the GNU getopt(3) routines to do this.

getopt can have following types of options.

 1) No-value options:
    -------------------------------------------------------------------------------------
    |#| Syntax                                 | Example                                |
    -------------------------------------------------------------------------------------
    |1| -o                                     | -v                                     |
    |2| --option                               | --verbose                              |
    -------------------------------------------------------------------------------------

 2) key-value pair options:
    -------------------------------------------------------------------------------------
    |#| Syntax                                  | Example                               |
    -------------------------------------------------------------------------------------
    |1| -k value.                               | -u username                           |
    |2| -kvalue  [Only when key is optional]    | -uusername                            |
    |3| --key=value                             | --user=username                       |
    -------------------------------------------------------------------------------------

Note: In this document, during explaining syntax:

  • Anything inside [ ] is optional parameter in the syntax/examples.
  • <value> is a place holder, which mean it should be substituted with an actual value.

HOW TO USE `getopt`?

Syntax: First Form

getopt optstring parameters

Examples

getopt "hv:t::" "-v 123"  # -t, -h is optional

# This is correct
getopt "hv:t::" "-v 123 -t123"  
getopt "hv:t::" "-v123 -t123"  # -v and 123 doesn't have whitespace

# -h takes no value.
getopt "hv:t::" "-h -v123"


# This is wrong. after -t can't have whitespace.
# Only optional params cannot have whitespace between key and value
getopt "hv:t::" "-v 123 -t 123"

# Multiple arguments that takes value.
getopt "h:v:t::g::" "-h abc -v 123 -t21"

# Multiple arguments without value
# All of these are correct
getopt "hvt" "-htv"
getopt "hvt" "-h -t -v"
getopt "hvt" "-tv -h"

Here h,v,t are the options and -h -v -t is how options should be given in command-line.

  • 'h' is a no-value option.
  • 'v:' implies that option -v has value and is a mandatory option. ':' means has a value.
  • 't::' implies that option -t has value but is optional. '::' means optional.

In optional param, value cannot have whitespace separation with the option. So, in "-t123" example, -t is option 123 is value


Syntax: Second Form

getopt [getopt_options] [--] [optstring] [parameters]

Here after getopt is split into five parts

  1. The command itself i.e. getopt
  2. the getopt_options, it describes how to parse the arguments. single dash long options, double dash options.
  3. --, separates out the getopt_options from the options you want to parse and the allowed short options
  4. The short options, is taken immediately after -- is found. Just like the Form first syntax.
  5. The parameters, these are the options that you have passed into the program. The options you want to parse and get the actual values set on them.

Examples

getopt -l "name:,version::,verbose" -- "n:v::V" "--name=Karthik -version=5.2 -verbose"


Syntax: Third Form

getopt [getopt_options] [-o options] [--] [optstring] [parameters]

Here after getopt is split into five parts

  1. The command itself i.e. getopt
  2. The getopt_options, it describes how to parse the arguments. single dash long options, double dash options.
  3. The short options i.e. -o or --options. Just like the Form first syntax but with option "-o" and before the "--" (double dash).
  4. --, separates out the getopt_options from the options you want to parse and the allowed short options
  5. The parameters, these are the options that you have passed into the program. The options you want to parse and get the actual values set on them.

Examples

getopt -l "name:,version::,verbose" -a -o "n:v::V" -- "-name=Karthik -version=5.2 -verbose"


GETOPT_OPTIONS

getopt_options changes the way command-line params are parsed.

Below are some of the getopt_options

Option: -l or --longoptions

Means getopt command should allow multi-character options to be recognised. Multiple options are separated by comma.

For example, --name=Karthik is a long option sent in command line. In getopt, usage of long options are like

getopt "name:,version" "--name=Karthik"

Since name: is specified, the option should contain a value

Option: -a or --alternative

Means getopt command should allow long option to have a single dash '-' rather than double dash '--'.

Example, instead of --name=Karthik you could use just -name=Karthik

getopt "name:,version" "-name=Karthik"


A complete script example with the code:

#!/bin/bash

# filename: commandLine.sh
# author: @theBuzzyCoder

showHelp() {
    # `cat << EOF` This means that cat should stop reading when EOF is detected
    cat << EOF  
Usage: ./installer -v <espo-version> [-hrV]
Install Pre-requisites for EspoCRM with docker in Development mode

    -h, -help,          --help                  Display help

    -v, -espo-version,  --espo-version          Set and Download specific version of EspoCRM

    -r, -rebuild,       --rebuild               Rebuild php vendor directory using composer and compiled css using grunt

    -V, -verbose,       --verbose               Run script in verbose mode. Will print out each step of execution.

EOF
# EOF is found above and hence cat command stops reading. This is equivalent to echo but much neater when printing out.
}


export version=0
export verbose=0
export rebuilt=0

# $@ is all command line parameters passed to the script.
# -o is for short options like -v
# -l is for long options with double dash like --version
# the comma separates different long options
# -a is for long options with single dash like -version
options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -a -- "$@")

# set --:
# If no arguments follow this option, then the positional parameters are unset. Otherwise, the positional parameters 
# are set to the arguments, even if some of them begin with a ‘-’.
eval set -- "$options"

while true
do
    case $1 in
        -h|--help) 
            showHelp
            exit 0
            ;;
        -v|--version) 
            shift
            export version=$1
            ;;
        -V|--verbose)
            export verbose=1
            set -xv  # Set xtrace and verbose mode.
            ;;
        -r|--rebuild)
            export rebuild=1
            ;;
        --)
            shift
            break;;
    esac
    shift
done

Running this script file:

# With short options grouped together and long option
# With double dash '--version'

bash commandLine.sh --version=1.0 -rV
# With short options grouped together and long option
# With single dash '-version'

bash commandLine.sh -version=1.0 -rV

# OR with short option that takes value, value separated by whitespace
# by key

bash commandLine.sh -v 1.0 -rV

# OR with short option that takes value, value without whitespace
# separation from key.

bash commandLine.sh -v1.0 -rV

# OR Separating individual short options

bash commandLine.sh -v1.0 -r -V

Please note: This is not my personal invention. Some of the contents are taken from man pages of getopt command. The examples however is my own and is well tested before publishing.

References:

  1. Dusty Mabe's blog

I tested the script in a mac(macOS Big Sur[Jan 2021]) and it does not work, not value is set

Like
Reply

To view or add a comment, sign in

More articles by Karthik Bhat K

  • Your own custom LEMP on Docker

    I have been trying to setup custom LEMP containers for EspoCRM application in Docker for past sometime. I didn't want…

  • Super Shortcuts in Linux Terminal

    Mac users can use Cmd instead of Ctrl In menu bar, click edit => click keyboard shortcuts to see your terminal…

    1 Comment
  • Python Meetup @ Manglore Infotech Solutions

    In this meet up session, we learnt how to use celery to run two or more tasks at the same time using RabbitMQ. We…

  • Python Workshop @ Sahyadri

    On Saturday March 5 2016, me and my colleague Santhosh Kadri conducted a workshop on basics of Python Programming…

  • Mangaluru Python Meetup Group

    On March 12, 2016, Mangaluru Python meetup was organised at Mangalore Infotech Solutions Pvt. Lmt.

Others also viewed

Explore content categories