UNIX Tutorials, Tips, Tricks and Shell Scripts

Examples of How to Pass Shell Script Arguments using "shift" and "getopts"


In this article we will explain shell script arguments and shell script options, the difference between the two, and how they get passed to a shell script. We will then look at how shell script arguments can be parsed within a shell script. We will also take a quick look at how to use the shell built-in command getopts to help us.


Shell Script Arguments

Anytime we execute a bash script we spawn a new instance of the bash shell which is the script itself. As part of creating this new shell instance a process takes place where all of the parent shell's environment is collected from a variety of files and system settings. These values are passed to the new shell as environmental variables. The new shell can also create its own variables and assign values to them. These new variables are called shell variables.

When we pass arguments to our shell script they are passed to the new shell as specific environmental variables.

The new shell has access to the following environmental variables:

$0 The name of the script itself.

$1, $2...$n These variables refer to numbered argument to the script, where n is the position of the argument on the command line. In the Bash shell you must use curly braces for position above 9 - such as ${10}. Some other shells require you to use the shift command to access any argument above the 9th argument. You have to continue to shift until you have processed them all.

TIP: In order to determine if your shell accepts the curly braces run:

sh -c 'echo ${300}' /usr/bin/*
... if it returns a valid /usr/bin filename - then you can use the curly braces, otherwise you will have to use the shift command.

$# The number of arguments specified on a command line. This variable does NOT count $0.

$* A single string ("$1 $2...$n") containing all of the positional parameters that are passed from the command line. These parameters are separated by the internal field separator character (the IFS shell variable - defaults to a whitespace).

$@ A sequence of strings ("$1", "$2", ... "$n") each positional parameter is separate from the others. Notice that this sequence of strings also does NOT include $0 described above.





Let's create the following script named testarg.sh:

#!/bin/bash

echo You passed $# arguments to your script $0
echo Here is your single string: $*
echo Your first argument is: $1
echo Your second argument is: $2
echo Your third argument is: $3
echo The entire set or arguments as a sequence of strings: $@

and we run it, we will see the following:

$ testarg.sh Livefire Labs rock
You passed 3 arguments to your script ./testarg.sh
Here they are: LiveFire Labs rock
Your first argument is: LiveFire
Your second argument is: Labs
Your third argument is: rock
The entire set or arguments as a sequence of strings: LiveFire Labs rock

It does appear that $* and $@ are the same. However, $* is viewed as a single value, while $@ is viewed as 3 separate values.

Let's move on...


Shell Script Options

Up until now we have referred to everything on the command line, other than the script name, as a shell script argument. In reality some of these arguments are known as command line options, also referred to as flags.

We use options to provide flexibility to our scripts. By using command line options we can change the way the script performs. Options are a specific type of shell script argument.

In the Linux world there are two types command line options: 1) a single letter preceded by a dash, known as short options; or 2) a single word preceded by a double dash, known as long options. Some of our options require their own arguments, but don't confuse these arguments with the entire command line argument string that we discussed above.

Let us look at the ls command as an example of passing arguments. We can enter a command such as:

$ ls
without any options or arguments this command simply lists all of the files/directory names within the current directory. If we add an argument like:

$ ls somefile.txt
it will simply list the name of the specified file, or files. Adding an option changes the behavior of the command, for example:

$ ls -l
The -l option tells the ls command to provide a long listing, showing more details about every file/directory in the current directory. Again, if we provide an additional argument:

$ ls -l somefile.txt
the ls command will provide a long listing of only the files, or files, specified.

Some options require an argument, such as the tar command:

$ tar -f myfile
The -f option says "use the following file", so the filename myfile is a required argument for the -f option.

Be aware that not all shell scripts will REQUIRE options or arguments. It simply depends on what the script is meant to do.

Now, we will get down to the nitty gritty....


Passing Arguments to a Shell Script

We will start with an example script, and then we will take a look at what it does. We can run this script with either short or long options:

$ mytest.sh -v -c -d test_directory
or
$ mytest.sh --c_option --version --directory test_directory
or
$ mytest.sh --help

The code for this script looks like:

#!/bin/bash

#############################################################
#This is an example of parsing options and arguments. 
#############################################################

C_OPT=0
DIR=`pwd`
VERSION="0.1Alpha"

while [ "$1" != "" ]; 
do
   case $1 in
    -c | --c_option )
        C_OPT=1
        ;;
    -v | --version )
        echo "Version: $VERSION"
        ;;
    -d | --directory )
        shift
        if [ -d "$1" ]
           then
             DIR="$1"
        else
           echo "$0: $1 is not a valid directory" >&2
           exit
        fi
        ;;
    -h | --help ) 
         echo "Usage: my_test [OPTIONS]"
         echo "OPTION includes:"
         echo "   -c | --c_option - flips the C_OPT variable from 0 to 1"
         echo "   -v | --version - prints out version information for this script"
         echo "   -d | --directory - requires user to specify a directory"
         echo "   -h | --help - displays this message"
         echo "   the command requires a filename argument"
         exit
      ;;
    * ) 
        echo "Invalid option: $1"
        echo "Usage: my_test [-c] [-v] [-d directory_name ]"
        echo "     -c flips the C_OPT variable from 0 to 1"
        echo "     -v prints out version information for this script"
        echo "     -d requires user to specify a directory"
        echo "     -h | --help - displays this message"
        echo "     the command requires a filename argument"
        exit
       ;;
  esac
  shift
done
There are 3 variables used in the script, they are set to a default value, which might be changed depending on what options we enter on the command line.

The while statement looks at $1, the first string in the $@ variable, checking to ensure that this variable is something other than a null value. If this is true then we drop into the case statement to see if $1 matches any of the options listed. If it matches one of the options the proper code is executed.

We use the shift command several times in this script. Each time you invoke shift, it "shifts" all the positional parameters to the left one position. So, $2 becomes $1, $3 becomes $2, $4 becomes $3, and so on.



In the -d case we shift so that now the argument, which should be a valid directory name is in the $1 variable. The if statement checks to see if $1 specifies a valid directory. If it does we set the $DIR variable to the $1 argument. If it is false, the user is warned that the specified directory is not valid and the script exits.

If there are options passed that are not valid, then there is a default case the "*" case prints a usage statement. As a good developer you want to provide a usage statement that gives the user enough information to determine what they did wrong and then re-run the script with the correct options and arguments.

After each time the case statement is processed there is a shift statement. This shifts the current option, $1, off the list, and moves to the next option that was provided on the command line. This ensures the proper parsing of our script's arguments within the while loop.

Remember, if you have any options, like our -d that require their own arguments you MUST place a shift at the beginning of that case section to ensure the proper parsing of the argument list.

Here are a couple of examples:

$ mytest.sh -v
Version:  0.1Alpha

Based on our argument, -v, the output is simply the value of the $VERSION variable, which is set near the top of our script.

If we provide an invalid directory name we will get an error message:

$ mytest.sh -d /data/LiveFire/bad-directory
mytest.sh: /data/LiveFire/bad-directory is not a directory

Notice that we gave it a full path to the non-existent directory, this will also work if we attempt to specify a non-existent directory in the current directory we are working in.

Next we will have a brief look at getopts...


Processing Shell Script Arguments and Options WITH getopts

Let's look at the same script using the getopts builtin command. A builtin command is one that is built into the shell. This provides for more efficient commands since they are already loaded into memory.

We will use the script we discussed above with a few changes to make use of getopts. One thing to note is that getopts does NOT deal with long options. We can run this script as:

$ mytest.sh -v -c -d test_directory
or
$ mytest.sh -h

The code for our new script looks similar, but there are significant differences.

#!/bin/bash

######################################################################
#This is an example of using getopts in a bash script. 
#############################################################

C_OPT=0
DIR=`pwd`


while getopts ":vchd:" opt; 
do
  case ${opt} in
    v ) # process option v
        echo "Version:  0.1Alpha" 
     ;;
    c) # process option c
        C_OPT=1 
     ;;
    d ) # process option d
        if [ -d "$OPTARG" ]
           then DIR=$OPTARG
        else
           echo "$0: $OPTARG is not a directory" >&2
           exit
        fi
        ;;
     : ) echo "$0: Must supply an argument to the -$OPTARG."
         exit 1
      ;;
    h ) echo "Here is some help for you..."
          echo "Usage: my_test [-c] [-v] [-d directory_name ]"
          echo "     -c flips the C_OPT variable from 0 to 1"
          echo "     -v prints out version information for this script"
          echo "     -d requires user to specify a directory to use"
          exit
      ;;
    \? ) echo "Invalid option -$OPTARG ignored"
          echo "Usage: my_test [-c] [-v] [-d directory_name ]"
          echo "     -c flips the C_OPT variable from 0 to 1"
          echo "     -v prints out version information for this script"
          echo "     -d requires user to specify a directory to use"
          exit
      ;;
  esac
done
The beginning of the script is about the same as the previous script. When we get to the while statement we see the getopts command serves as the conditional of the while.

As part of the getopts command we have a list of the possible options, :vchd:, that can be used with our script. So, the while statement loops through each of these options, setting the variable $opt to the value of the current option. The $opt variable is used in the case statement that makes up the body of the while statement.

Notice that in this version of the script the case pattern does NOT start with a the hyphen, the value of $opt is just the option letter. We also do NOT use shift in our script. That is because getopts uses the $OPTIND variable to keep track of the index of the next option to be processes. There is also a variable, $OPTARG, that holds the argument for the current option if one is required.


Usage Considerations

The maximum number of options and arguments are set within the Linux system. Unless you are using thousands of arguments you should not have to worry about exceeding the set limit.

The one thing that is a bit more difficult is to specify an argument without an option. We see this in "ls somefile" - the "ls" command displays information about the specified file, without any options. We can do this in our getopts script, but it takes a bit more effort to deal with that situation when we are using the getopts command.


In Summary ...

Being able to pass shell script arguments into our scripts adds a lot of power and flexibility to any script we write. It is well worth the time and effort to make sure we add the capability to use shell script arguments to all of our scripts.

Using getopts to pass shell script arguments does provide a well-defined mechanism to parse options and arguments. However, writing our own parsing code gives us much more flexibility in dealing with the shell script arguments we pass from the command line.




Do you need to learn UNIX shell scripting and get practice writing & running scripts...on a REAL SERVER? If you are ready to move past the basics, either of these online courses is a good place to start...

UNIX and Linux Operating System Fundamentals contains a very good "Introduction to UNIX Shell Scripting" module, and should be taken if you are new to the UNIX and Linux operating system environments or need a refresher on key concepts.

UNIX Shell Scripting is a good option if you are already comfortable with UNIX or Linux and just need to sharpen your knowledge about shell scripting and the UNIX shell in general.

Both courses include access to an Internet Lab system for completing the course's hands-on exercises, which are used to re-enforce the key concepts presented in the course. Any questions you may have while taking the course are answered by an experienced UNIX technologist.

Thanks for reading, and happy scripting!!!