Thursday, August 30, 2012

getopts - How to pass command line options to shell script?



   In an earlier article, we discussed how to pass command line arguments to shell script and access them using positional parameters. In this, we will see how to use the getopts command to pass command line options to shell scripts. Command line options are the options or switches passed to a command. For example, -l, -r, -t are some examples of the command line options passed to the ls command.

Types:
  Command line options can be classified into 3 types:
  1. Options which does not take arguments:
    • ls -l    #no arg for -l
  2. Options which take arguments:
    • cut -d","  file #arg "," for -d
  3. Options + Additional command line arguments:
    • ls -l file1 file2 file3 
Syntax:
The syntax of the getopts command is:
 getopts optstring name
-> optstring - the string which contains the list of options expected in the command line
-> name - the variable name which is used to read the command line options one by one.

Env Variables:
getopts command makes use of 2 environment variables:
  OPTARG : contains the argument value for a particular command line option.
  OPTIND : contains the index of the next command line option.

  Let us now discuss  with examples how to use the getopts command:
Examples:
1. Options without arguments:
  Let us create a bash shell script to display the current date and month. This script will take 2 command line options:
    d - To display date of the current day
    m - To display month of the current month.
$ cat disp.sh
#!/bin/bash

while getopts dm name
do
        case $name in
          d)dopt=1;;
          m)mopt=1;;
          *)echo "Invalid arg";;
        esac
done

DT=`date '+%d %b'`

if [[ ! -z $dopt ]]
then
    echo "Date is: " ${DT/ */}
fi
if [[ ! -z $mopt ]]
then
    echo "Month is: " ${DT/* /}
fi

shift $(($OPTIND -1))
  Checkout how the getopts is used here.It is used with the while command and an internal switch within while. "getopts dm name" - "dm" are the options expected by the getopts command and "name" is the variable which contains the options. switch case will be present for every option which can be expected and an appropriate action is performed. In our case, on encountering a particular command line option, an approriate flag variable is set. Outside the while loop, depending on the flags, the requisite result is printed. We will discuss the shift command at the end.

On running the script:
$ ./disp.sh
$
  Nothing gets printed when no command line options are provided. If needed, you can add one more option "u" in the script to display the usage of the command.
 Running the shell script with options:
$ ./disp.sh -d
Date is:  30
$ ./disp.sh -d -m
Date is:  30
Month is:  Aug
2. Options with arguments:
    In this example, let us update the same script so that "-d" option can take a value. If "-d" is provided a value n, the date and month displayed will be n days from the current date(GNU Date). For example, if the current date is 30th Aug, and if a value of 2 is provided with "-d", the date and month displayed will be "01" and "Sep".
$ cat disp.sh
#!/bin/bash

while getopts d:m name
do
        case $name in
          d)dopt=$OPTARG;;
          m)mopt=1;;
          *)echo "Invalid arg";;
        esac
done

if [[ ! -z $dopt ]]
then
    DT=`date '+%d %b'  -d "$dopt days"`
    echo "Date is: " ${DT/ */}
fi

if [[ ! -z $mopt ]]
then
    echo "Month is: " ${DT/* /}
fi

shift $(($OPTIND -1))
   See the difference!!! "d:m"- a colon after an option indicates the option contains an argument. And the appropriate argument is present in the environment variable OPTARG.
$ ./disp.sh -d 3 -m
Date is:  01
Month is:  Sep
Similarly, if you want both the options to contain arguments, it should be: "d:m:"

3. getopts with invalid options:
    The above shell script can accept only two options: d & m. What if an invalid option is provided?
$ ./disp.sh -c
./disp.sh: illegal option -- c
Invalid arg
   What happened? Though our expected message "Invalid arg" got printed, also a system generated message came along. This system generated can be supressed by adding a colon in the beginning of the optstring.
$ cat disp.sh
#!/bin/bash

while getopts :d:m name
do
        case $name in
          d)dopt=$OPTARG;;
          m)mopt=1;;
          *)echo "Invalid arg";;
        esac
done
Running the script with invalid option:
$ ./disp.sh -c
Invalid arg
4. Options with arguments + Additional command line parameters:
  In this script, the script will be updated to also accept some additional command line arguments. An additional argument will be provided for which a welcome message will be printed.
$ cat disp.sh
#!/bin/bash

while getopts d:m name
do
        case $name in
          d)dopt=$OPTARG;;
          m)mopt=1;;
          *)echo "Invalid arg";;
        esac
done

if [[ ! -z $dopt ]]
then
    DT=`date '+%d %b'  -d "$dopt days"`
    echo "Date is: " ${DT/ */}
fi

if [[ ! -z $mopt ]]
then
    echo "Month is: " ${DT/* /}
fi

shift $(($OPTIND -1))

echo "Welcome, " $1
Running the script with additional argument:
$ ./disp.sh -d 2 -m Guru
Date is:  01
Month is:  Sep
Welcome,  Guru
   OPTIND contains the index of the next command line option. Keep in mind, all the command line options provided to the shell script will be present in the variables $1, $2 , $3 and so on. How can we now distinguish where the command line options get over and from where the additional arguments start? This is where the OPTIND helps. In this example, when the getopts has processed, OPTIND will contain 4, pointing to the next(4th) argument. And hence, in order to access the command line arguments, it is better if we flush of all the command line options and its arguments prior to the additional arguments. And hence the shift command.
shift $(($OPTIND -1))
  will become "shift 3" which in other words will shift/flush the first 3 command line options, and hence after the shift "Guru" will now be $1. It is always a good practice to clean off the command line options at the end of the script.

5 comments:

  1. Little correction

    DT=`date '+%d %b' -d "2 days"

    Should be

    DT=`date '+%d %b' -d "$dopt days"

    ReplyDelete
    Replies
    1. thanks suresh for pointing it out, updated now.

      Delete
  2. Best documentation I have seen so far on GETOPTS concept.

    ReplyDelete
  3. Explained in simple language,gave lot of clarity!

    ReplyDelete