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 city1d
corresponds to city2h
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:
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.