Have you ever tried writing a Bash script and felt stuck figuring out how to handle command-line options like -f or --file?
Well, good news! Enter getopts—a built-in Bash tool that makes your scripts dynamic, user-friendly, and efficient. And the best part is, it handles the tricky parts of scripting like validating inputs and managing errors, so you don’t have to!
However, getting the hang of it can feel overwhelming when you’re just starting out. Which is why in this guide, we’ll demystify getopts step by step, showing you how to simplify your scripts, avoid common pitfalls, and elevate your Bash scripting skills.
So let’s get into it.
Sidenote: Want to dive deeper into Bash? Check out my complete BASH scripting course:
You'll learn Shell Scripting fundamentals, master the command line, and get the practice and experience you need to go from beginner to being able to get hired as a DevOps Engineer, SysAdmin, or Network Engineer!
I guarantee that this is the most comprehensive and up-to-date online resource to learn Bash Scripting.
With that out of the way, let’s dive into this 5-minute tutorial…
Handling command-line arguments in Bash can be frustrating, especially if you’re manually parsing options from the $@ variable.
It’s like reinventing the wheel every time. You end up writing repetitive code to figure out which flags were passed, check if they have values, and ensure the input even makes sense. And let’s face it: it’s easy to make mistakes along the way.
The good news? Bash offers tools to handle this for you.
This brute-force method involves writing your own logic to loop through $@, figure out each flag, and validate inputs manually. While it’s flexible, it’s tedious and prone to errors—especially for larger scripts.
GetoptThis is an external tool that supports advanced features like long options (--help). However, its behavior varies between systems—what works on Linux might fail on macOS—making portable scripts a challenge.
GetoptsNot to be confused with almost identical external tool getopt, getopts is a built-in Bash command that simplifies handling short options like -u or -p.
It processes options one by one, validates inputs, and assigns values to variables automatically. However, because it’s built into Bash, getopts is consistent across systems, making it portable and beginner friendly.
To really see the difference though, let’s compare manual parsing to getopts.
For example
Imagine your script needs to handle a username (-u) and a password (-p). Here’s how you’d do it manually:
for arg in "$@"; do
case $arg in
-u|--username)
username="$2"
shift
;;
-p|--password)
password="$2"
shift
;;
*)
echo "Unknown option: $arg"
;;
esac
doneAs you can see, this works, but it’s clunky. You’re repeating yourself and handling every edge case manually.
Compare that instead to using getopts:
while getopts "u:p:" opt; do
case $opt in
u) username="$OPTARG" ;;
p) password="$OPTARG" ;;
\?) echo "Invalid option: -$OPTARG" ;;
esac
doneLook at how much cleaner that is! With getopts, you’re writing less code and getting built-in validation and error handling for free.
Here’s a quick comparison at how getopts stacks up against the other methods:
Here’s a quick comparison of how getopts stacks up against the other methods:
| Feature | Manual Parsing | getopt |
getopts |
|---|---|---|---|
| Input validation | Custom logic needed | Advanced, customizable | Built-in, simpler |
| Portability | High | Low | High |
| Ease of use | Low | Moderate | High |
| Long options support | No | Yes | No |
| Error handling | Manual | Advanced | Automatic |
If you’re looking for an easy, reliable way to handle command-line options in your Bash scripts, getopts is the tool for the job.
It’s simple, portable, and saves you from the hassle of writing repetitive parsing logic. While it doesn’t support long options, it’s a perfect fit for most scenarios—especially if you’re just getting started with scripting.
Ready to dive in? Let’s break down how getopts works and how you can use it to make your scripts more dynamic and efficient.
It might seem a bit daunting at first, but once you understand the basics, getopts is surprisingly intuitive.
Here’s the general syntax for getopts:
getopts "optstring" variableLet’s take a look at what this code is doing.
The optstring defines the options your script accepts.
Each letter in the string represents an option, like -a or -b. But what if an option needs additional information? That’s where the colon (:) comes in.
:) immediately after itFor example
u: means the -u option requires an argument, like -u Aliceh means the -h option doesn’t need an argument. It just acts as a standalone flagThe variable is where the option currently being processed is stored. Inside your script, you’ll typically use a case statement to handle the variable and decide what to do for each option.
For example
If -u Alice is passed, the variable will hold u, and the argument (Alice) will be stored in $OPTARG. This makes it easy to manage multiple options in a clean, organized way.
A note on usage
getoptsis almost always used inside awhileloop. The loop then ensures that every option passed to the script is processed, one at a time, until there are no more options left. This structure keeps your code efficient and easy to maintain.
Now that we’ve covered the basics, let’s put it all together.
Here’s an example script that uses getopts to handle two options:
-u for specifying a username (requires an argument)-h for displaying a help message (no argument needed)And here’s the code:
#!/bin/bash
while getopts ":u:h" opt; do
case $opt in
u)
username="$OPTARG"
echo "Username: $username"
;;
h)
echo "Help: This script accepts -u <username> and -h for help."
;;
\?)
echo "Invalid option: -$OPTARG"
;;
:)
echo "Option -$OPTARG requires an argument."
;;
esac
doneHere’s what’s happening in the script:
The string "u:h" tells getopts what options the script expects:
u: indicates that -u requires an argumenth indicates that -h doesn’t need an argumentWhile the while loop processes each option in turn. The case statement inside the loop handles what to do for each option:
u: When the -u option is passed, the argument that follows it is stored in $OPTARG and assigned to the username variableh: When the -h option is passed, the script displays the help message\?: This catches invalid options (like -z) and shows an error message:: This catches cases where an option that requires an argument is missing one, like -u without a valueNow that we have our code, let’s make sure everything works as expected.
Save the script as example.sh and make it executable with this command:
chmod +x example.shNow, try running it with the following inputs. I’ll explain what’s happening in each case.
./example.sh -u AliceThe -u option is passed, and the argument Alice is assigned to the username variable. The script recognizes this input as valid and outputs the username.
Output:
Username: Alice./example.sh -hThe -h option is passed, which is a standalone flag. The script detects this and displays the help message. No arguments are needed for this option, so it processes successfully.
Output:
Help: This script accepts -u <username> and -h for help../example.sh -zThe -z option is not defined in the optstring making it invalid. Because of this, the \? case in the case statement catches it and displays an error message.
Output:
Invalid option: -z./example.sh -uThe -u option is passed, but it requires an argument (like a username). Since no argument is provided, the : case in the case statement is triggered, and an error message is displayed.
Output:
Option -u requires an argument.-u Alice assigns Alice to the username variable, and -h triggers the help message-z, are gracefully handled by the \? case-u without a value, are caught by the : case and flagged with an appropriate error messageNow that we’ve covered the basics, let’s look at how to handle multiple options and arguments in a single script.
This is where getopts really shows its power, as it can process combinations of options, handle arguments for some options while ignoring them for others, and even deal with optional arguments if needed.
optstringIn the optstring, you simply add more letters and colons to define additional options. Each letter represents a flag, and colons indicate whether an option requires an argument.
For example
abc: means:
-a is a standalone flag-b is another standalone flag-c requires an argument (e.g., -c value)You can define as many options as needed, and getopts will process them all.
Let’s say we want to write a script that:
-u option for a username (requires an argument)-p option for a password (requires an argument)-v option for verbose mode (no argument needed)Here’s how the script might look:
#!/bin/bash
while getopts "u:p:v" opt; do
case $opt in
u)
username="$OPTARG"
echo "Username: $username"
;;
p)
password="$OPTARG"
echo "Password: $password"
;;
v)
verbose=true
echo "Verbose mode enabled"
;;
\?)
echo "Invalid option: -$OPTARG"
;;
:)
echo "Option -$OPTARG requires an argument."
;;
esac
doneNow that we have that, let’s see how the script handles multiple options.
Save it as example.sh, make it executable, and test with these inputs.
Also, since we’ve already covered tests for invalid options (like -z) and missing arguments (like -u without a value) in earlier examples, we’ll focus on scenarios unique to handling multiple options here.
./example.sh -u Alice -p secret123 -vAll options are processed in a single run. The -u and -p options set their respective values, and the -v option enables verbose mode.
Output:
Username: Alice
Password: secret123
Verbose mode enabled-v./example.sh -vThe -v option is processed. Since it doesn’t require an argument, the script simply enables verbose mode and outputs a confirmation message.
Output:
Verbose mode enabledoptstring can include as many options as needed, and colons (:) allow you to specify which options require argumentsgetopts processes them all in the order they’re provided\?) and missing arguments (:) to make your scripts user-friendlySpeaking of error handling, let’s explore how to combine these features to improve your scripts even more.
Error handling is a core strength of getopts, and it’s even better when paired with user-friendly features like a help message (-h).
By handling errors effectively, you ensure that users receive clear guidance when they make mistakes, making your script easier to use.
In this section, we’ll explore how to integrate error handling with informative messages and explain how to gain more control over error behavior by using a leading colon (:) in the optstring.
The key to combining helpful feedback with error handling is using a usage function. This function provides clear instructions on how to use your script and is called whenever an error occurs.
Here’s how it works:
usage function at the top of your scriptusage function whenever getopts detects an invalid option (\?) or a missing argument (:)For example:
#!/bin/bash
# Define the usage function
usage() {
echo "Usage: $0 -u <username> -p <password> [-h for help]"
}
# Process options with getopts
while getopts "u:p:h" opt; do
case $opt in
u)
username="$OPTARG"
;;
p)
password="$OPTARG"
;;
h)
usage
exit 0
;;
\?)
echo "Error: Invalid option -$OPTARG"
usage
exit 1
;;
:)
echo "Error: Option -$OPTARG requires an argument."
usage
exit 1
;;
esac
doneSo let’s break down what's happening.
The usage function displays clear instructions on how to use the script. This ensures users always know what the script expects, even if they make a mistake.
However, when an invalid option (\?) is detected, the script:
Invalid option -$OPTARG)1 to indicate an errorWhen a missing argument (:) is detected, the same process is followed
Option -$OPTARG requires an argument)1When the -h option is passed:
0 (indicating success):)By default, getopts automatically prints error messages for missing arguments.
However, if you want to suppress these messages and handle errors manually, you can add a leading colon (:) to the optstring.
Without a leading colon ("u:p:h"):
getopts prints an error message for missing arguments$opt to ?With a leading colon (":u:p:h"):
getopts does not print an error message$opt to : and places the affected option in $OPTARG, allowing the script to handle it manuallyFor example:
Let’s modify the script to manually handle errors instead of letting getopts print them using silent error mode (:)
#!/bin/bash
# Define the usage function
usage() {
echo "Usage: $0 -u <username> -p <password> [-h for help]"
}
# Process options with silent error mode
while getopts ":u:p:h" opt; do
case $opt in
u)
username="$OPTARG"
;;
p)
password="$OPTARG"
;;
h)
usage
exit 0
;;
:)
echo "Error: Option -$OPTARG requires an argument."
usage
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG"
usage
exit 1
;;
esac
doneNow, instead of getopts printing an error when a required argument is missing, the script detects the issue itself and provides a custom message.
Let’s test the script to see how errors and help messages are integrated.
./example.sh -hOutput:
Usage: ./example.sh -u <username> -p <password> [-h for help]✅ The -h option correctly triggers the usage function.
./example.sh -zOutput:
Error: Invalid option -z
Usage: ./example.sh -u <username> -p <password> [-h for help]✅ The script detects an invalid option (-z) and prints an error along with the usage instructions.
getopts default mode)If the script is using this optstring ("u:p:h", without :):
while getopts "u:p:h" opt; doRunning
./example.sh -uOutput (default getopts behavior, no leading colon):
getopts: option requires an argument -- u🚨 Since no leading colon (:) is used, getopts prints this error automatically instead of allowing the script to handle it.
: in the optstring)If the script is modified to use this optstring (":u:p:h", with :):
./example.sh -uRunning
./example.sh -uOutput (with : in the optstring, silent error mode):):
Error: Option -u requires an argument.
Usage: ./example.sh -u <username> -p <password> [-h for help]✅ Since a leading colon (:) is present, getopts does not print its own error message. Instead, the script detects the missing argument manually and prints a custom error message.
usage function ensures users always know how to properly use the scriptusage alongside error messages for invalid options (\?) or missing arguments (:) provides helpful feedback without overwhelming users0 for help, 1 for errors) is a best practice for professional scriptsEven though getopts is a powerful tool, it’s easy to run into issues, especially if you’re just starting out. So let’s go over some common pitfalls and how to avoid them, so your scripts can run smoothly.
If you forget to add a colon (:) after an option in the optstring, getopts won’t treat it as requiring an argument. This can lead to unexpected behavior, like not detecting missing arguments.
For example
while getopts "u:p" opt; do
case $opt in
u)
echo "Username: $OPTARG"
;;
p)
echo "Password: $OPTARG"
;;
esac
doneSo, if you run:
./example.sh -uInstead of flagging an error, getopts processes -u without expecting an argument, leaving $OPTARG empty.
Always add a colon after options that require arguments. In this case, "u:p:" ensures both -u and -p expect arguments.
while getopts "u:p:" opt; doNote: If an option requires an argument but is missing one, getopts will print an error automatically unless the optstring starts with :.
Using ":u:p:" allows the script to detect missing arguments manually by checking if $opt is set to :. This provides more control over error handling.
OPTARG variableSome developers assume $OPTARG will always contain a value, but it’s only set for options that require arguments. If an option doesn’t need an argument, $OPTARG will be empty.
For example
while getopts "v" opt; do
case $opt in
v)
echo "Verbose mode: $OPTARG"
;;
esac
doneBecause of this, running ./example.sh -v outputs:
Verbose mode:Avoid relying on $OPTARG for options that don’t require arguments. Use it only for options defined with a colon in the optstring.
Skipping the \? or : cases in your case statement can leave your script vulnerable to invalid inputs or missing arguments. This results in unexpected behavior or unclear error messages.
For example
while getopts "u:p:" opt; do
case $opt in
u|p)
echo "Option: $opt, Argument: $OPTARG"
;;
esac
doneWhat happens:
./example.sh -z does nothing./example.sh -u silently ignores the missing argumentAlways include the \? and : cases to handle errors gracefully and provide feedback to the user.
getopts only processes options and their arguments, leaving positional arguments (arguments without a - or --) unhandled. This can lead to confusion if users pass additional inputs.
For example
./example.sh -u Alice file.txtIn this case, the file.txt argument is ignored.
After the getopts loop, process the remaining arguments using the $@ or $* variables, like so:
shift $((OPTIND - 1))
echo "Positional arguments: $@"getopts for long optionsWe mentioned this earlier, but getopts only supports short options (like -u), not long options (like --username). Trying to use getopts for long options won’t work.
If you need to support long options, consider using the external getopt tool instead. For scripts that only need short options, stick to getopts.
optstring for colons to avoid misconfigured arguments$OPTARG only for options that require arguments\? and :) to make your script robustgetopts is for short options—use getopt if you need long option supportSo as you can see, getopts is an incredibly handy tool for handling command-line options in Bash scripts, offering simplicity, portability, and built-in error handling.
Now that you understand how to use it, the next step is to put it into practice! Experiment with creating scripts that use multiple options, handle errors gracefully, and solve real-world problems like file management or automation.
The more you practice, the more confident you’ll become. Start small, build your skills, and soon parsing options will feel effortless.
Don’t forget - if you want to fast-track your Bash knowledge and get as much hands-on practice as possible, then check out my complete BASH scripting course:
Updated for 2025, you'll learn Shell Scripting fundamentals, master the command line, and get the practice and experience you need to go from beginner to being able to get hired as a DevOps Engineer, SysAdmin, or Network Engineer!
Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students, and working DevOps professionals.
If you enjoyed this post, then check out my other Bash guides and tutorials!
