Do you wish your Linux shell scripts would handle command-line options and arguments more gracefully? The Bash
getopts builtin lets you parse command-line options with finesse—and it’s easy too. We show you how.
Introducing the getopts builtin
Passing values into a Bash script is pretty a pretty simple matter. You call your script from the command line or from another script and provide your list of values behind the script name. These values can be accessed inside your script as variables, starting with
$1 for the first variable,
$2 for the second and so on.
But if you want to pass options to a script, the situation quickly becomes more complex. When we say options we mean the options, flags, or switches that programs like
ls can handle. They’re preceded by a dash “
-” and usually act as an indicator to the program to turn on or off some aspect of its functionality.
ls command has over 50 options, mainly related to formatting its output. The
-X (sort by extension) option sorts the output alphabetically by file extension. The
-U (unsorted) option lists by directory order.
Options are just that—they’re optional. You don’t know which options—if any—the user is going to choose to use, and neither do you know in which order they may list them on the command line. This increases the complexity of the code required to parse the options.
Things become more complicated still if some of your options take an argument, known as an option argument, For example, the
ls -w (width) option expects to be followed by a number, representing the maximum display width of the output. And of course, you might be passing other parameters into your script that are simply data values, that are not options at all.
getopts handles this complexity for you. And because it is a builtin, it’s available on all systems that have the Bash shell, so there’s nothing to install.
Note: getopts Not getopt
There’s an older utility called
getopt . This is a small utility program, not a builtin. There are many different versions of
getopt with differing behaviors, whereas the
getops builtin follows POSIX guidelines.
getopt isn’t a builtin it doesn’t share some of the automatic benefits that
getopts does, such as handling whitespace sensibly. With
getopts, the Bash shell is running your script and the Bash shell is doing the option parsing. You don’t need to invoke an external program to handle the parsing.
The tradeoff is
getopts doesn’t handle double-dashed, long-format option names. So you can use options formatted like
-w but not ”
---wide-format.” On the other hand, if you have a script that accepts the options
-b , and
getopts lets you combine them like
-bac and so on.
We’re discussing and demonstrating
getopts in this article, so make sure you add the final “s” to the command name.
A Quick Recap: Handling Parameter Values
This script doesn’t use dashed options like
-b . It does accept “normal” parameters on the command line and these are accessed inside the script as values.
#!/bin/bash # get the variables one by one echo "Variable One: $1" echo "Variable Two: $2" echo "Variable Three: $3" # loop through the variables for var in "$@" do echo "$var" done
The parameters are accessed inside the script as variables
Copy this text into an editor and save it as a file called “variables.sh.” We’ll need to make it executable with the
chmod command. You’ll need to do this step for all of the scripts we discuss. Just substitute the name of the appropriate script file each time.
chmod +x variables.sh
If we run our script with no parameters, we get this output.
We passed no parameters so the script has no values to report. Let’s provide some parameters this time.
./variables.sh how to geek
As expected, the variables
$2 , and
$3 have been set to the parameter values and we see these printed.
This type of one-for-one parameter handling means we need to know in advance how many parameters there will be. The loop at the bottom of the script doesn’t care how many parameters there are, it always loops through them all.
If we provide a fourth parameter, it isn’t assigned to a variable, but the loop still handles it.
./variables.sh how to geek website
If we put quotation marks around two of the words they’re treated as one parameter.
./variables.sh how "to geek"
If we’re going to need our script to handle all combinations of options, options with arguments, and “normal” data type parameters, we’re going to need to separate the options from the regular parameters. We can achieve that by placing all options—with or without arguments—before the regular parameters.
But let’s not run before we can walk. Let’s look at the simplest case for handling command-line options.
getopts in a
while loop. Each iteration of the loop works on one option that was passed to the script. In each case, the variable
OPTION is set to the option identified by
With each iteration of the loop,
getopts moves on to the next option. When there are no more options,
false and the
while loop exits.
OPTION variable is matched against the patterns in each of the case statement clauses. Because we’re using a case statement, it doesn’t matter what order the options are provided on the command line. Each option is dropped into the case statement and the appropriate clause is triggered.
The individual clauses in the case statement make it easy to perform option-specific actions within the script. Typically, in a real-world script, you’d set a variable in each clause, and these would act as flags further on in the script, allowing or denying some functionality.
Copy this text into an editor and save it as a script called “options.sh”, and make it executable.
#!/bin/bash while getopts 'abc' OPTION; do case "$OPTION" in a) echo "Option a used" ;; b) echo "Option b used" ;; c) echo "Option c used" ;; ?) echo "Usage: $(basename $0) [-a] [-b] [-c]" exit 1 ;; esac done
This is the line that defines the while loop.
while getopts 'abc' OPTION; do
getopts command is followed by the options string. This lists the letters that we’re going to use as options. Only letters in this list can be used as options. So in this case,
-d would be invalid. This would be trapped by the
?) clause because
getopts returns a question mark “
?” for an unidentified option. If that happens the correct usage is printed to the terminal window:
echo "Usage: $(basename $0) [-a] [-b] [-c]"
By convention, wrapping an option in brackets “
” in this type of correct usage message means the option is optional. The basename command strips any directory paths from the file name. The script file name is held in
$0 in Bash scripts.
Let’s use this script with different command line combinations.
./options.sh -a -b -c
./options.sh -ab -c
As we can see, all of our test combinations of options are parsed and handled correctly. What if we try an option that doesn’t exist?
The usage clause is triggered, which is good, but we also get an error message from the shell. That might or might not matter to your use case. If you’re calling the script from another script that has to parse error messages, it’ll make it more difficult if the shell is generating error messages too.
Turning off the shell error messages is very easy. All we need to do is put a colon ”
: ” as the first character of the options string.
Either edit your “options.sh” file and add a colon as the first character of the options string, or save this script as “options2.sh”, and make it executable.
#!/bin/bash while getopts ':abc' OPTION; do case "$OPTION" in a) echo "Option a used" ;; b) echo "Option b used" ;; c) echo "Option c used" ;; ?) echo "Usage: $(basename $0) [-a] [-b] [-c]" exit 1 ;; esac done
When we run this and generate an error, we receive our own error messages without any shell messages.
Using getopts With Option Arguments
getopts that an option will be followed by an argument, put a colon ”
: ” immediately behind the option letter in the options string.
If we follow the “b” and “c” in our options string with colons,
getopt will expect arguments for these options. Copy this script into your editor and save it as “arguments.sh”, and make it executable.
Remember, the first colon in the options string is used to suppress shell error messages—it has nothing to do with argument processing.
getopt processes an option with an argument, the argument is placed in the
OPTARG variable. If you want to use this value elsewhere in your script, you’ll need to copy it to another variable.
#!/bin/bash while getopts ':ab:c:' OPTION; do case "$OPTION" in a) echo "Option a used" ;; b) argB="$OPTARG" echo "Option b used with: $argB" ;; c) argC="$OPTARG" echo "Option c used with: $argC" ;; ?) echo "Usage: $(basename $0) [-a] [-b argument] [-c argument]" exit 1 ;; esac done
Let’s run that and see how it works.
./arguments.sh -a -b "how to geek" -c reviewgeek
./arguments.sh -c reviewgeek -a
So now we can handle options with or without arguments, regardless of the order in which they’re given on the command line.
But what about regular parameters? We said earlier we knew we’d have to put those on the command line after any options. Let’s see what happens if we do.
Mixing Options and Parameters
We’ll change our previous script to include one more line. When the
while loop has exited and all of the options have been handled we’ll try to access the regular parameters. We’ll print out the value in
Save this script as “arguments2.sh”, and make it executable.
#!/bin/bash while getopts ':ab:c:' OPTION; do case "$OPTION" in a) echo "Option a used" ;; b) argB="$OPTARG" echo "Option b used with: $argB" ;; c) argC="$OPTARG" echo "Option c used with: $argC" ;; ?) echo "Usage: $(basename $0) [-a] [-b argument] [-c argument]" exit 1 ;; esac done echo "Variable one is: $1"
Now we’ll try a few combinations of options and parameters.
./arguments2.sh -a dave
./arguments2.sh -a -c how-to-geek dave
So now we can see the problem. As as soon as any options are used, the variables
$1 onwards are filled with the option flags and their arguments. In the last example,
$4 would hold the parameter value “dave”, but how do you access that in your script if you don’t know how many options and arguments are going to be used?
The answer is to use
OPTIND and the
shift command discards the first parameter—regardless of type—from the parameter list. The other parameters “shuffle down”, so parameter 2 becomes parameter 1, parameter 3 becomes parameter 2, and so on. And so
$2 , and so on.
If you provide
shift with a number, it’ll take that many parameters off the list.
OPTIND counts the options and arguments as they are found and processed. Once all the options and arguments have been processed
OPTIND will be one higher than the number of options. So if we use shift to trim
(OPTIND-1) parameters off the parameter list, we’ll be left with the regular parameters in
That’s exactly what this script does. Save this script as “arguments3.sh” and make it executable.
#!/bin/bash while getopts ':ab:c:' OPTION; do case "$OPTION" in a) echo "Option a used" ;; b) argB="$OPTARG" echo "Option b used with: $argB" ;; c) argC="$OPTARG" echo "Option c used with: $argC" ;; ?) echo "Usage: $(basename $0) [-a] [-b argument] [-c argument]" exit 1 ;; esac done echo "Before - variable one is: $1" shift "$(($OPTIND -1))" echo "After - variable one is: $1" echo "The rest of the arguments (operands)" for x in "$@" do echo $x done
We’ll run this with a mix of options, arguments, and parameters.
./arguments3.sh -a -c how-to-geek "dave dee" dozy beaky mick tich
We can see that before we called
$1 held “-a”, but after the shift command
$1 holds our first non-option, non-argument parameter. We can loop through all of the parameters just as easily as we can in a script with no option parsing.
It’s Always Good To Have Options
Handling options and their arguments in scripts doesn’t need to be complicated. With
getopts you can create scripts that handle command-line options, arguments, and parameters exactly like POSIX compliant native scripts should.