Simple Usage of Optional Flags in Bash Script
14-05-2023
![cookie-banner](/images/data/blog/suoofibs-banner.webp)
what is optional flags? #
one of the powerful features of command-line programs is the capability to pass multiple arguments during the time of execution. that way, the program can run straight-up the way we wanted to without having another STDIN
session to take the user input.
there are multiple ways of passing multiple arguments and parsing them so the program can read them without hassle. one of the best approaches is to have optional flags. these flags are represented by character letters preceded by a hyphen, -u duke64
, or by a word preceded by a double hyphen, --username duke64
. the parameter value of each corresponding flag is defined next to it by whitespace.
one of the program that is designated for this feature is the getopts
utility, which retrieves options and option arguments from a list of parameters. getopts
is a replacement for GNU/Linux utility getopt
from a standalone program to become a shell’s built-in command. even though both serve the same main function, one difference is that getopts
cannot read word-based flags, while getopt
can. to overcome that while still maintaining the script’s readability and simplicity from using getopt
, we will use positional arguments instead, such as $1
, that will be passed and parsed manually later on.
simple use case #
there are two cases that we will demonstrate: the use of getopts
in testflag-1.sh
and positional arguments in testflag-2.sh
. both scripts are going to contain the same mockup functions that are later on going to be tested; they are:
usage()
for listing the possible flagsinvalid()
for throwing erorr message of unknown flag(s)generate()
as the non-argument flag function to generate the whole output message, with the argument$1
for the name of the user,$2
for the age, and$3
for the sex
1#!/bin/bash
2#----------
3usage() {
4 echo "list of available options goes here"
5 exit 0; }
6invalid() {
7 echo "testflag: detecting improper option"
8 echo "try option '-h' for more information"
9 exit 1; }
10generate() {
11 echo "my name is $1 ($3)" | grep -E --color "$1|$3"
12 echo "i am $2 years old" | grep -E --color "$2"
13 exit 0; }
14#----------
getopts #
in getopts
, a collection of the valid option letters, OPTSTRING
, will determine whether the flag is argument-based or not. that distinction is defined by the usage of a trailing semicolon :
, which indicates that an argument variable called OPTARG
will set that particular argument from the flag. here are the following snippets on the overall usage of getopts
:
14#----------
15while getopts "n: a: s: g h" opt; do
16 case $opt in
17 n) n="${OPTARG}" ;;
18 a) a="${OPTARG}" ;;
19 s) s="${OPTARG}" ;;
20 g) opt_sub="generate" ;;
21 h) usage ;;
22 *) invalid ;;
23 esac
24done
25#----------
26case $opt_sub in
27 "generate") [[ "$n" && "$a" && "$s" ]] && generate "$n" "$a" "$s" || invalid ;;
28esac
29#----------
30usage
the argument-based flag in OPTSTRING
that is going to take the user’s arguments are n:
for the name of the user, a:
for the age, and s:
for the sex. as one of the improvement from getopts
, it will also throws an error if an argument from the argument-based flag is not supplied, which is set to an explicit null
.
each OPTSTRING
option, later on, will be iterated from the while
loop and checking it to its corresponding letter via case
statement. if the provided flag by the user does not exist in the OPTSRING
option and one of the case
statements, then the option will be thrown to the default
case to perform an invalid()
function.
lastly, there’s a case
statement that will check if the user wanted to perform generate()
using the -g
flag. with the AND logical operator (&&
) in the if
statement, all the other mandatory argument-based flags must be provided, or the script will throw the invalid()
function to warn the user about an incomplete argument. if all the arguments don’t meet any cases defined in the case
statement, the script will display the usage()
function instead.
here is one case for using getopts
in testflag-1.sh
on testing the overall flag to produce the desired output as well as the built-in error handling:
positional arguments #
in positional arguments, one of the approaches we take is to create an array called args
first that act as a pool of valid options, including letters and their corresponding keywords. while that array stores all the valid arguments, the special variable $#
will store the total arguments that are being passed to the script, which requires at least one flag for the while
loop to run functionally. here are the following snippets on the overall usage on positional arguments.
14#----------
15args=(n name a age s sex g generate h help)
16while [ $# -gt 0 ]; do
17 if [[ "$1" == -* ]]; then
18 raw_opt=$(printf "%s\n" "$1" | tr -d '-')
19 if [[ $raw_opt ]]; then
20 if [[ $(echo "${args[@]}" | grep -ow "$raw_opt" | wc -w) -eq 1 ]]; then
21 case $1 in
22 -n | --name) n="$2" ;;
23 -a | --age) a="$2" ;;
24 -s | --sex) s="$2" ;;
25 -g | --generate) opt="generate" ;;
26 -h | --help) usage ;;
27 esac
28 else
29 echo "$0: illegal option -- $raw_opt"
30 invalid
31 fi
32 fi
33 fi
34 shift
35done
36#----------
37case $opt in
38 "generate") [[ "$n" && "$a" && "$s" ]] && generate "$n" "$a" "$s" || invalid ;;
39esac
40#----------
41usage
there are nested if
statements that we will be walking through. the first statement in line 17
is to check whether the passed raw arguments are using the valid flag format. it is determined by checking each argument that at least has the first character as a hyphen. the use of double hyphens for a word-based flag isn’t a problem here since it uses a trailing wildcard. once it passes, the flag in $1
will be truncated and stored in raw_opt
variable so that it can be compared just by word or letter from the args
array.
while the statement at line 19
is to check if the flag isn’t null
, the last if
statement is used to check whether the flag exists within the args
pool. the usage of wc
is going to tell if the grep
command is successful; the output should be 1
, as it matches the overall operator to be equal to 1
. keeping the positional arguments still in $1
as the flag character or word and $2
as the flag’s value is possible by using the shift
command, which moves the positional arguments to always begin in $1
for all the flag, despite the iteration from the while
loop.
unlike using getopts
, here we need to create our error handling of invalid flag options that are being provided, which is why we have several if
statements to do so. since it can’t read a default
case like getopts
, we can put those invalid cases in the else
block if the flag isn’t matched any of the args
flags. then the rest is the same as the getopts
section, which has a specific case
statement to perform the generate()
function if all the mandatory flags are provided.
here are one case for using positional arguments as the optional flag in testflag-2.sh
on testing the overall flag, both letter-based and word-based, to produce the desired output as well as the custom error handling:
![](/images/data/blog/suoofibs-2.gif)
the verdict is in #
to have a script that handles kinds of flags, both getopts
and positional arguments servers well in terms of parsing it. while getopts
more towards simplicity and readability of the overall workflow, using positional arguments and parsing them manually may give you some form of flexibility, especially the capability to read word-based flags like we used to see on other programs in GNU/Linux. good luck with whatever stuff you’re doing
resources #
references #
- Bash Tutorial: getopts | stackchief
- Using –getopts to Pick Up Whole Word Flags - Bash | AppsLoveWorld
- How to Pass Command Line Arguments to Bash Script | Baeldung
- Beginners Guide to Use getopts in Bash Scripts & Examples | GoLinuxCloud
- getopts(1p) — Linux Manual Page | man7