A bash script rarely runs standalone. Rather, you often pass one or more arguments to the script from the command line to modify its run-time behavior. The script then reads the supplied arguments, parse and process them accordingly. In more advanced cases, you may want to pass command-line options to your script (e.g., "-h" or "-f my.txt") to optinally change the default settings of the script. In this tutorial let's find out how you can pass command-line arguments and how you can handle command-line options in a bash shell script.
The command-line arguments that are passed to a bash script are called positional parameters because how these arguments are referenced inside a script depends on the position of the arguments in the command line. More specifically, the command-line arguments are referenced by digits that are incrementing in the order of appearance in the command line (e.g., $1 for the first argument, $2 for the second argument, ..., $N for the N-th argument). When you access the N-th argument where N contains more than one digits (e.g., the 10-th argument), you need to surround N with {} (e.g., ${10}). $0 is a special bash parameter that stores the name of a bash script.
The following bash script example checks if the first (second) argument exists, and prints its value if it does.
if [ -z $1 ]; then
echo "The first argument is not set"
else
echo "The first argument: $1"
fi
if [ ! -z $2 ]; then
echo "The second argument: $2"
else
echo "The second argument is not set"
fi
If you want to check the total number of arguments passed to a bash script, you can use a special bash parameter called $#.
if [ $# -ne 3 ]; then
echo "You need to provide three arguments"
exit 1
fi
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
echo "Number of arguments must be between 1 and 2"
exit 1
fi
Another way to check command-line arguments in a bash script is by referencing a special bash parameter called $@, which expands to a list of supplied command-line arguments. Since $@ is an iterable list, you can use for loop to read each argument in the list. For example:
pos=1
for arg in "$@"; do
echo "$pos-th argument : $arg"
(( pos += 1 ))
done
Note that you need to surround $@ with quotes in case any command-line argument contains whitespaces. When you run this script, you will see the following output.
$ ./script.sh 100 linux "hi there"
1-th argument : 100
2-th argument : linux
3-th argument : hi there
The previous two methods access ordinary command-line arguments. In some cases, however, you may want to handle command-line options in a shell script, which may or may not be specified from the command line. The command-line options are typically single letters preceded by a single hyphen. Some options are used standalone (e.g., -h), while other options and their arguments are provided together (e.g., -f file.txt). When a shell script reads command-line options, what matters is not the position of an option in the command line, but rather what option flag is used.
Bash provides a built-in function called getopts to handle such command-line options. It's much easier to learn getopts with examples.
while getopts 'hi:f:' OPTION; do
case "$OPTION" in
h)
echo "Option h is given"
;;
i)
iarg="$OPTARG"
echo "Argument provided with option i: $iarg"
;;
f)
farg="$OPTARG"
echo "Argument provided with option f: $farg"
;;
*)
echo "Usage: $0 [-h] [-i value] [-f filename]" >&2
exit 1
;;
esac
done
In the example script above, the while loop iterates each of all possible command-line options and processes them. The string that follows getopts indicates possible option flags accepted by this script. Our example accepts "-h", "-i" and "-f" flags. The ":" sign after an option flag (e.g., "-i" and "-f") indicates that the option requires an argument. For those options that take an argument, an actual argument value is passed as $OPTARG. The case statement with "*" is a catch-all statement which is matched when any unsupported command-line option is entered.
Note that the entire while loop can be skipped if no command-line option is supplied from the command line.
In the final scenario, let's say a given script takes (optional) command-line options as well as (required) positional parameters. The following example script handles such a scenario, which combines the example scripts from Method One and Method Three.
while getopts 'hi:f:' OPTION; do
case "$OPTION" in
h)
echo "Option h is given"
;;
i)
iarg="$OPTARG"
echo "Argument provided with option i: $iarg"
;;
f)
farg="$OPTARG"
echo "Argument provided with option f: $farg"
;;
*)
echo "Usage: $0 [-h] [-i value] [-f filename]" >&2
exit 1
;;
esac
done
shift "$(($OPTIND -1))"
if [ -z $1 ]; then
echo "The first argument is not set"
else
echo "The first argument: $1"
fi
if [ ! -z $2 ]; then
echo "The second argument: $2"
else
echo "The second argument is not set"
fi
One important statement in the above example is the following line.
shift "$(($OPTIND - 1))"
$OPTIND is a special bash variable which is set to 1 upon launch of this script. As each option field is parsed, $OPTIND is incremented by one. shift is a built-in function which moves positional parameters. When shift N is called, it discards the first-N positional parameters from the command line. Thus the above statement discards all command-line options (if any) that are processed, leaving only subsequent ordinary command-line arguments, so that we can reference them using $1, $2, etc.
For example, when you run the script with the following arguments:
$ ./script -f my.txt argument1 argument2
You will see this output as expected:
Argument provided with option f: my.txt
The first argument: argument1
The second argument: argument2

