Working with arguments in Bash Scripting

Bash scripting has many special shell variables like $*, $#, $?, etc. to help users write more powerful and versatile scripts. One can code for many scenarios, using these shell variables, which are otherwise not possible.

One of the common requirements is to write a more generic code and run it specifically using the arguments supplied at the run time. Since script users can’t be always trusted with supplying all arguments properly, its beneficial to adjust script to properly check for conditions like how many arguments are supplied, if arguments are proper, etc. Since bash also does not natively offer a way to write parameters for the scripts, you also need to process the arguments in the correct order. For this, bash offers special variables $1, $2$9 as positional parameters.

These variables hold the positional arguments supplied to the script. When we say positional, it means that they are referred with respect to their positions, in which they are presented/provided. So $1 stores the first argument, $2 stores second argument, $3 stores third…and so on. Of generally speaking, 9 arguments are sufficient to cover most needs. If you need to supply more arguments, you can go for parameter expansion like ${10}, ${11}, etc. However, you might want to reconsider your requirements or using arrays.

Consider below code where we are working with three arguments inside our script:

#!/bin/bash

echo "first argument   : $1"
echo "second argument  : $2"
echo "third argument   : $3"

Below is one of the sample runs:

$ ./passing-arguments.sh One Two Three
first argument   : One
second argument  : Two
third argument   : Three
$

However as we mentioned, that users cannot be always trusted with supplying all correct information. What if the user is supplying less arguments that the script is expecting or more arguments than the script needs. Let’s observe the run in such cases:

$ ./passing-arguments.sh One
first argument   : One
second argument  :
third argument   :
$ ./passing-arguments.sh One Two Three four
first argument   : One
second argument  : Two
third argument   : Three

In second case, four gets assigned to $4 by bash, but since we are not using it within script, it does not affect the script execution. So it is safe to neglect. In first case, where user supplied only one argument, it is correctly got assigned to $1. However, $2 and $3 were not assigned anything as user didn’t supplied any. this affects our script functionality, so we need to account for errors like these so as to make sure that script is executed correctly. For this, we can use special shell variable $#, to tell us the number of arguments supplied by the user. We can then compare it against our requirements using an if condition and then decide if we need to execute the code or not.

Consider below code where script checks if the number of arguments is three and if not, exit with code of 1 :

#!/bin/bash

# verify that we have correct number of arguments
if [ ! $# -eq 3 ];then
   echo "Please supply correct number of arguments."
   exit 1
fi

# process arguments supplied
echo "first argument   : $1"
echo "second argument  : $2"
echo "third argument   : $3"

This will cover the scenarios if the user is supplying more or less number of arguments than expected:

$ ./passing-arguments.sh One
Please supply correct number of arguments.
$ ./passing-arguments.sh One Two Three four
Please supply correct number of arguments.
$ ./passing-arguments.sh One Two Three
first argument   : One
second argument  : Two
third argument   : Three

However, rather than printing un-helpful messages, it can be useful to print more meaningful message indicating what went wrong. One of the ways is to use special shell variable $0, to refer to the current script using its filename.

Consider below code to indicate more meaningful error message:

#!/bin/bash

# verify that we have correct number of arguments
if [ ! $# -eq 3 ];then
   echo "Usage:$0 <arg1> <arg2> <arg3>"
   exit 1
fi

# process arguments supplied
echo "first argument   : $1"
echo "second argument  : $2"
echo "third argument   : $3"

Consider the message printed, if we run the script with incorrect number of arguments:

$ ./passing-arguments.sh One
Usage:./passing-arguments.sh <arg1> <arg2> <arg3>

It is a good idea to use more programming notations like [] to represent either array or optional arguments, <> to represent mandatory arguments, etc. Do note that since arguments are positional in nature, you cannot have optional arguments before mandatory arguments.

Another good idea is to echo few lines indicating what each argument is expected to do. So instead of just echoing arg1, arg2, etc., echo what users need to understand before submitting values.

#!/bin/bash

# verify that we have correct number of arguments
if [ ! $# -eq 3 ];then
   echo "Usage:$0 <arg1> <arg2> <arg3>"
   echo "arg1 - Mandatory - Name of the server you want to connect to. This must be a string."
   echo "arg2 - Mandatory - Port of the server you want to connect to. This must be a number within 1024 and 65000."
   echo "arg3 - Mandatory - Name of the database you want to connect to. This must be a string."
   exit 1
fi

# process arguments supplied
echo "first argument   : $1"
echo "second argument  : $2"
echo "third argument   : $3"

Consider the message now printed, if we run the script with incorrect number of arguments:

$ ./passing-arguments.sh One
Usage:./passing-arguments.sh <arg1> <arg2> <arg3>
arg1 - Mandatory - Name of the server you want to connect to. This must be a string.
arg2 - Mandatory - Port of the server you want to connect to. This must be a number within 1024 and 65000.
arg3 - Mandatory - Name of the database you want to connect to. This must be a string.

We can accept dynamic number of arguments using special variables like $* and $@. This allows us to code for scenarios where we do not know in advance of how many arguments are needed. When unquoted inside script, these variables expands into arguments.

Consider below code where these variables are unquoted inside script:

#!/bin/bash

# example Using $* 
for item in $*
do
  echo "Received: $item"
done

# example using $@
for item in $@
do
  echo "Received: $item"
done

This will give us same output if we supplied arguments in below form:

$ ./dynamic-arguments.sh One Two Three
Received: One
Received: Two
Received: Three
Received: One
Received: Two
Received: Three
$ ./dynamic-arguments.sh "One" "Two" "Three"
Received: One
Received: Two
Received: Three
Received: One
Received: Two
Received: Three
$ ./dynamic-arguments.sh "One Two Three"
Received: One
Received: Two
Received: Three
Received: One
Received: Two
Received: Three
$ ./dynamic-arguments.sh "One Ones" "Two" "Three"
Received: One
Received: Ones
Received: Two
Received: Three
Received: One
Received: Ones
Received: Two
Received: Three

In third case, it appears that we can replace arguments with an array of strings. However, arrays are supplied and accepted differently in the Bash. Its not an one-to-one replacement. In fourth case, note that even though whitespace is part of our argument value One Ones, it is expanded into two arguments, which is unintended. We can code for this possibility using quoted $* i.e. "$*". However, it becomes a single word. Quoted $@ i.e. "$@", continues to behave as unquoted $@. Consider below code:

#!/bin/bash

# example Using $*
echo "using - quoted \$*" 
for item in "$*"
do
  echo "Received: $item"
done

# example using $@
echo "using - quoted \$@"
for item in "$@"
do
  echo "Received: $item"
done

Consider the output now:

$ ./dynamic-arguments.sh "One Ones" "Two" "Three"
using - quoted $*
Received: One Ones Two Three
using - quoted $@
Received: One Ones
Received: Two
Received: Three

The use of dynamic arguments is more common when we are calling another script/function from inside main script, rather than asking from users.

Also to be noted that we could have used word function in-place of script. The special variables, with the exception of $0, gets reassigned when calling function or script from another function or script.

Consider below code, where we have defined a function called two-args and expecting two arguments to be supplied. However, when we are calling it, we pass only one argument (and we pass the first argument supplied to script i.e. $1):

#!/bin/bash

function two-args {
    echo "function name   : $0"
    echo "first argument  : $1"
    echo "second argument : $2"
}

# process arguments supplied
echo "first argument   : $1"
echo "second argument  : $2"
echo "third argument   : $3"

# calls function two-args
two-args $1

Consider the run below:

$ ./passing-arguments.sh One Two Three
first argument   : One
second argument  : Two
third argument   : Three
function name   : ./passing-arguments.sh
first argument  : One
second argument :

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s