How to Parse Command Line Arguments in Bash

Introduction

Bash scripts take in command-line arguments as inputs both sequentially and also, parsed as options. The command-line utilities use these arguments to conditionally trigger functions in a Bash script or selectively choose between environments to execute the script. In Bash, these are configured in different ways.

In this article, we will be exploring several ways of creating these arguments for consumption and demonstrate a practical example for the implementation of command-line arguments.

Positional Command-Line Arguments

Command-line arguments are read in a positional manner, from position $1, $2, ..$n. The pattern $ followed by an integer is a reserved combination to represent the command-line arguments.

$0 denotes the name of the script.

Here's a pictorial representation of positional command-line arguments used to invoke a script:

Consider the following script, arg_intro.sh, where the arguments are printed according to their positions:

#!/bin/bash

echo "Script name is: $0"
echo "Arg1 is $1"
echo "Arg1 is $2"
echo "Arg1 is $3"
echo "-----------"
echo "All args: $*"
echo "All args count: $#"

This shell script is executed with the arguments as shown below:

$ bash arg_intro.sh runtime inputs
Script name is: ./arg_intro.sh
Arg1 is runtime
Arg1 is inputs
Arg1 is 
-----------
All args: runtime inputs
All args count: 2

Parsing Complex Arguments using getopt

In the previous section, you understood how the positional arguments are passed into the script. This method doesn't hold well if there's an increase in the number of arguments or there's a conditional requirement for the assignment of the variables. In such cases, a solid framework needs to be in place.

The command-line utility getopt solves this problem by providing syntax and options to define and parse the arguments.

Here's a gist on how to define arguments using the getopt.

Defining the Options

There are two kinds of arguments when passing them to a command-line utility. These include:

  • Short Arguments - These are defined by a single character, prepended by a hyphen. For example, -h may denote help, -l may denote a list command.
  • Long Arguments - These are whole strings, prepended by two hyphens. For example, --help denotes help, and --list denotes list.

Consider this script test_options.sh, where set up arguments with the getopt utility:

#!/bin/bash

SHORT=c:,d:,h
LONG=city1:,city2:,help
OPTS=$(getopt --alternative --name weather --options $SHORT --longoptions $LONG -- "$@") 

The short arguments are passed to the --options flag of the getopt utility while the long arguments are passed to the --longoptions flag. In the code above, we have three short options:

  • c corresponds to city1
  • d corresponds to city2
  • h corresponds to help.

The help option in the command-line utilities doesn’t take any values, and hence it doesn't have a colon attached to it.

  • Single colon (:) - Value is required for this option
  • Double colon (::) - Value is optional
  • No colons - No values are required

In the code above, the arguments c and d (followed by a colon) require the value to be sent, while the option h doesn't require any arguments (no colon).

Shifting to Other Arguments

The getopt utility puts the input arguments to an organized positional output.

For example, let's tweak test_options.sh to print our options:

#!/bin/bash

SHORT=c:,d:,h
LONG=city1:,city2:,help
OPTS=$(getopt -a -n weather --options $SHORT --longoptions $LONG -- "$@")
echo $OPTS

Upon passing arguments to this script, it results in the following output:

$ bash test_options.sh --city1 Paris --city2 NewYork
--city1 'Paris' --city2 'NewYork' --

Here's a visual representation of the output generated by the script and a demonstration of the QA environments:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Notice that the utility has added a trailing double hyphen (--) denoting the end of the output. Now, this output can be treated as positional arguments, and loops are written to iterate through each of them. This is where the shift keyword is helpful.

The shift keyword takes an additional argument of how many positions of the arguments to move the cursor to. To parse the next argument, the shift command has a number 2 assigned to it, which would switch to the next argument.

Consider the test_options_looping.sh script which uses shift to capture and print the values of the options in our arguments:

#!/bin/bash

SHORT=c:,d:,h
LONG=city1:,city2:,help
OPTS=$(getopt -a -n weather --options $SHORT --longoptions $LONG -- "$@")

eval set -- "$OPTS"

while :
do
  case "$1" in
    -c | --city1 )
      city1="$2"
      shift 2
      ;;
    -d | --city2 )
      city2="$2"
      shift 2
      ;;
    -h | --help)
      "This is a weather script"
      exit 2
      ;;
    --)
      shift;
      break
      ;;
    *)
      echo "Unexpected option: $1"
      ;;
  esac
done

echo $city1, $city2

When the script is called by passing the valid arguments, it results in the printing of the two assigned variables as:

$ bash test_options_looping.sh --city1 Paris --city2 NewYork
Paris, NewYork

Notice how the script exited when it encountered a double hyphen (--).

With the learnings from above, let's make an example CLI weather app that accepts and parses the city names as arguments.

Example - Building a Command-Line Weather App

The command-line weather app is retrieves data from https://wttr.in, a public and free weather web app created by Igor Chubin. Our shell script makes a GET request with the curl utility to retrieve a weather report. If two arguments are passed, it gives a comparison between the weather in two cities.

The script contains all the above-mentioned components, including the getopt utility call. Based on the input arguments, there is a check on the number of valid arguments.

If no arguments are passed, the help() function is executed and exited. If not, the script proceeds and checks for the options in the switch case.

Based on the inputs, the if-else condition is evaluated and the curl to the URL is made by replacing the string argument with inputs.

Let's create a script called weather.sh with the following code:

#!/bin/bash

help()
{
    echo "Usage: weather [ -c | --city1 ]
               [ -d | --city2 ]
               [ -h | --help  ]"
    exit 2
}

SHORT=c:,d:,h
LONG=city1:,city2:,help
OPTS=$(getopt -a -n weather --options $SHORT --longoptions $LONG -- "$@")

VALID_ARGUMENTS=$# # Returns the count of arguments that are in short or long options

if [ "$VALID_ARGUMENTS" -eq 0 ]; then
  help
fi

eval set -- "$OPTS"

while :
do
  case "$1" in
    -c | --city1 )
      city1="$2"
      shift 2
      ;;
    -d | --city2 )
      city2="$2"
      shift 2
      ;;
    -h | --help)
      help
      ;;
    --)
      shift;
      break
      ;;
    *)
      echo "Unexpected option: $1"
      help
      ;;
  esac
done

if [ "$city1" ] && [ -z "$city2" ]
then
    curl -s "https://wttr.in/${city1}"
elif [ -z "$city1" ] && [ "$city2" ]
then
    curl -s "https://wttr.in/${city2}"
elif [ "$city1" ] && [ "$city2" ]
then
    diff -Naur <(curl -s "https://wttr.in/${city1}" ) <(curl -s "https://wttr.in/${city2}" )
else
    curl -s https://wttr.in
fi

The script is tested with the following cases:

i) Test case 1 - When a single city is provided

$ bash weather.sh -city1 NewYork

ii) Test case 2 - When two cities are provided:

$ bash weather.sh -city1 NewYork -city2 NewDelhi

iii) Test case 3 - When none of the city arguments are mentioned:

$ bash weather.sh

As expected, our script works fine and it returns the weather report for the input cities. When no arguments are passed, the script calls the help() function to return the list of supported arguments.

Conclusion

In this article, we have covered the basics of passing the positional and parsing complex optional arguments from a shell script.

The example utility that we created calls the weather API based on the arguments that we provided to the script.

Last Updated: May 11th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Sathiya Sarathi GunasekaranAuthor

Pythonist 🐍| Linux Geek who codes on WSL | Data & Cloud Fanatic | Blogging Advocate | Author

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms