BASH 07 – Command-Line Options

Jarret B

Well-Known Member
Staff member
Joined
May 22, 2017
Messages
339
Reaction score
372
Credits
11,689
Some scripts and programs can take options from the command line. These can be very specific for the operation of a program or script.

There are two ways to handle options: placement or parameters.

Placement

We covered placement options before, but let’s look a little closer to see how this works.

When we run a script, the Bash shell has built-in variables for the commands on the command-line. Let’s look at the layout of a command:

command option1 option2 option3 … option n

The variable $0 references the command. We store Option1 in the variable $1 and so on.

Let’s make a test script called ‘options.sh’ and add the code:

#!/bin/bash
echo $0
echo $1
echo $2
echo $3


If we make the script executable, we can run it with the following options: ‘./options.sh 1 second 3’. The output should be:

./options.sh
1
second
3


You can see that as stated, the variable $0 contains ‘./options.sh’, $1 holds ‘1’, $2 has ‘second’ and $3 represents the option ‘3’.

You can use these parameters to perform functions, as long as the user places the appropriate functions in the correct order. If you look at a command such as ‘cp’, there are options, but what remains is the SOURCE and DESTINATION to copy a file. We must list the SOURCE before the DESTINATION for the program to work properly.

If you want to know how many options there are, not counting $0, the system stores the value in the variable $#. The previous example would return a value of 3 for $#.

The problem arises if we can enter multiple parameters in any order. We need to go through every parameter and handle each value for a parameter or be able to know the option is present.

Parameters (getopts)

With ‘getopts’, we can cycle through each parameter and its argument, if any.

Let’s assume we had a script that could find prime numbers from a beginning point to an ending point. The default beginning point is 1 and the default ending point is 1000. If the user does not enter a beginning point or ending point, then we can use the defaults, or use the numbers that the user specifies. Here, if we enter two numbers, we know the first one should be the beginning and the second number the ending. But what if we only enter one number? The script can accept a parameter ‘-s #’ for the starting number and a ‘-e #’ for the ending number. If one is not present, then we use the default.

Now that you understand the use of the parameters and their options, let’s look at the structure.

Structure of ‘getopts’

The following is a basic structure of ‘getopts’:

while getopts "<parameters>" opt; do
case $opt in
<parameter1>)command 1
command2
command3
commandn;;
<parameter2>) command1
command2
command3
commandn;;
<parametern>) command1
command2
command3
commandn;;
:) command;;
?) command;;
*) command;;
esac
done


Let’s look at this in more detail. If we continue our example script of requiring a starting and ending number, we can actually use example parameters.

The first line we specify our parameters in the <parameters> section. We could have a line like:

while getopts "<s:e:>" opt; do

Here, I specify there two possible parameters: ‘s’ and ‘e’ and each takes a value behind it. Since a colon :)) follows the parameter, we know it takes an option. Let’s say I had a parameter ‘f’ that specified to only find the ‘first’ prime number and stop, then the line would be:

while getopts "<fs:e:>" opt; do

The parameter ‘f’ could be anywhere in the section with the others. The values can be in any order.

The second line, ‘case $opt in’ specifies the variable that we used in the first line (opt). We can change the value in Line 1 as long as it matches in Line 2. Try to be consistent and use the same variable name if you can.

The third line lets specify the parameter we are looking matching from our list. If we do them in order, which isn’t necessary, it would be ‘f)’. We can then have multiple lines of commands that manage the parameter ‘-f’.

Let’s look back at Line 1. If we start the ‘<parameters>’ with a colon :)), then this means we will handle errors ourselves. If the system handles any errors, then you should receive a cryptic error message and the program quits. It is usually better that the script writer manage the errors.

Managing the script errors is why there are three parameters of ‘:’, ‘*’ and ‘?’.

The parameter ‘:’ is used to manage a missing option. Say a user entered ‘-s’ with no starting value, this error parameter would catch it. It may not be the ‘-s’ parameter, but the ‘-e’ parameter. Here, a simple error message would work. The value of the option is in $OPTARG.

By specifying the case ‘?)’, it catches invalid parameters entered by the user.

The parameter for ‘*’ manages all other errors that can occur. This section can handle all ‘generic’ errors, like displaying a help screen of the script usage. You can just as easily display a help screen for all errors.

Do note that we end the last line in each case with double semi-colons (;;). Do not forget to terminate the case section or the case section continues until a double semi-colon is found.

End the Case section with ‘esac’ and the ‘while ‘section with ‘done’.

Now, let’s look at a real example.

Example of ‘getopts’

Let’s make a script that will input either a value in inches or meters. We can then convert the value to the other measurement.

The parameter will either be ‘-m’ if the value is in meters or ‘-i’ if in inches. We follow the parameter by a value to be converted.

The following is an example we can use:

#!/bin/bash
found=0
while getopts ":m:i:" opt; do
case $opt in
m) V=$OPTARG
found=1
answer=$(echo "($V * 39.3700787402)" | bc)
echo "$V meters is $answer inches";;
i) V=$OPTARG
found=1
answer=$(echo "($OPTARG * 0.0254)" | bc)
echo "$V inches is $answer meters";;
:) echo " "
echo "The parameter $OPTARG is missing a value!"
exit 1;;
?) echo " "
echo "$OPTARG is invalid!"
exit 1;;
*) echo " "
echo "clength: Missing Operand"
echo "Invalid options provided."
echo "The proper uage is:"
echo "clength [option] value"
echo "-m value <in meters to convert to inches>"
echo "-i value <in inches to convert to meters>"
echo " "
exit 1;;
esac
done
if [ $found -eq 0 ]; then
echo You need a parameter!
exit 1
fi
exit 0


After the ‘shebang’, I am setting a variable named ‘found’ to ‘0’. The variable is a placeholder that we will go over at the end of the script. It is a basic handler for an error that is not managed in the ‘getopts’ structure.

In the ‘getopts’ line of ‘while getopts ":m:i:" opt; do’ we are accepting two parameters of ‘-m’ or ‘-i’ that each require a value. We also specify that we are handling the error management by the initial colon :)).

Then we get into the ‘case’ section to manage the parameters.

The first section is for the parameter ‘-m’ and its value. The code is:

m) V=$OPTARG
found=1
answer=$(echo "($V * 39.3700787402)" | bc)
echo "$V meters is $answer inches";;


The first line takes the value after the ‘-m’ and places it into the variable ‘V’. Remember, the ‘$OPTARG’ variable holds the value after the parameter. If the value is not present, we skip the section because it is an entry error by the user.

Looking at the second line, you see this variable ‘found’ again. I am setting the value to 1 because the user has entered a valid parameter and value.

The next line converts the metric unit of meters into inches and places the answer into the variable ‘answer’. We must pass the equation to the command ‘bc’ so we can represent the value with decimal values, or else it displays a whole number.

The last line will echo the answer, specifying the original value in meters with an answer in inches.

The next section for ‘i)’ is the exact same, with only a difference in the equation used to calculate the answer.

Next follows the error handling for invalid parameters, missing parameters, and everything else that can happen.

You should take note that the ‘everything else’ section, noted by the ‘*)’ lists a basic usage to the script. In a future article, we can handle this as a function to call it multiple times in all the sections and not have to repeat all the lines in each section.

Be aware that at the end of each error handling section is a line starting with ‘exit’ and a number. Once we handle these lines, the program ends with an error code of 1. You can specify a different number for each section so you can manage the various error codes, but this is a simple script.

The last section is why we set the variable ‘found’ to ‘0’. The code is:

if [ $found -eq 0 ]; then
echo You need a parameter!
exit 1
fi


If we just run the script with no parameters, then the script generates no errors and the program ends with no error code. If we get to the end of the script and no it processed no parameter, then the variable ‘found’ still contains ‘0’. We can check if it does and give an error message. Again, we could give the error and then print out the proper usage of the script, but I’m trying to keep the script short for now.

Testing the Script

Place the code into a script file and set the proper permissions and make it executable. Pass a few values to it and see how it works.

Try a few incorrect entries and check the error correction.

You should also try just running the script and see how the last section with the variable ‘found’ works out.

Finally, there is a test you can try to see that the ‘getopts’ works on each parameter one at a time. The way we managed the ‘case’ statements, it echos a message after processing each one. Try the command:

script-name.sh -m 10 -i 100

You should get a line printed out for each parameter. This shows how the script processes each parameter individually.

To also see the difference, switch the parameters:

script-name.sh -i 100 -m 10

The script manages the parameters in the order that we place them on the line.

Conclusion

Practice with ‘getopts’ since passing parameters and values to your script can allow for a more ‘professional’ looking script, as well as more powerful.

Definitely be aware of handling parameters with your scripts, since this can be very handy.
 


Top