LFCS 126 ๐ง Shell Scripting โ Arguments and Parsing
The previous chapters covered positional parameters, special variables, and conditionals. This chapter focuses on the practical problem of argument parsing: how a script accepts options like -v, --verbose, or -f file, validates them, and behaves accordingly. The LFCS exam tests the getopts builtin, the manual while/case/shift pattern, and the difference between short options (-v) and long options (--verbose). The critical point is that getopts handles short options only. Long options require a manual loop or an external tool like getopt (not getopts). The exam tests the getopts syntax because it is POSIX and available everywhere.
Key point: getopts is a shell builtin that parses short options. The syntax is while getopts "vf:" opt; do ... done. The first argument is the option string: letters that take no argument, and letters followed by a colon that take an argument. The second argument is the variable name that getopts sets on each iteration. The $OPTARG variable holds the argument value. The $OPTIND variable holds the index of the next argument. The getopts command returns false when there are no more options to parse.
Why argument parsing requires a specific pattern
The positional-only problem. A script that accepts only positional arguments (./script.sh arg1 arg2) has no way to express optional flags or named parameters. The caller must know the exact order, and adding a new option changes the interface for every caller. Flags (-v, -f file) are the standard interface for scripts with configurable behavior.
The manual parsing problem. A script can parse flags manually with a while/case/shift loop. This works for both short and long options, but the logic is error-prone: missing values, unknown flags, and combined flags (-vf) must be handled explicitly. The getopts builtin handles the common cases: short options, option values, and error reporting. It does not handle long options (--verbose), which require a manual loop or the external getopt command.
The validation problem. An argument may be a file path, a number, or a domain name. The script must validate the value before using it. The getopts builtin does not validate the value; it only extracts it. The validation is the script’s responsibility. The -f option that expects a filename must check that the file exists. The -n option that expects a number must check that the value is numeric.
The help problem. Every script with options should provide a -h or --help flag that prints the usage. The usage message documents the options and the expected arguments. The getopts pattern handles -h like any other flag: the script prints the usage and exits.
The trade-off. getopts is POSIX and available everywhere. It handles short options only. The external getopt command (with the --longoptions flag) handles long options but is not POSIX and has different behavior on different systems. A manual while/case/shift loop handles anything but requires more code. The exam tests getopts because it is the standard tool.
a. The getopts syntax and the option string
The getopts command is a builtin. It parses the positional parameters for options. The first argument is the option string. The second is the variable name that receives the option letter on each iteration.
#!/bin/bash
while getopts "vf:" opt; do
case "$opt" in
v) verbose=true ;;
f) file="$OPTARG" ;;
\?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
:) echo "Option -$OPTARG requires an argument" >&2; exit 1 ;;
esac
done
The option string "vf:" declares two options. The v is a flag that takes no argument. The f: is an option that takes an argument. The colon after f is the marker. The option string can also start with a colon, which changes the error handling: ":vf:" makes getopts silent and sets $OPTARG to the problematic option.
The variable $OPTARG holds the value of the option’s argument. The variable $OPTIND holds the index of the next argument to be parsed. After the loop, the script can access the remaining non-option arguments with shift $((OPTIND - 1)).
shift $((OPTIND - 1))
echo "Remaining arguments: $@"
The $OPTIND starts at 1. The $((OPTIND - 1)) is the number of arguments consumed by the options. The shift removes them, leaving the non-option arguments in $@.
b. The manual loop for long options
getopts does not handle long options. The manual while/case/shift loop is the standard pattern for long options. The loop checks the first argument, processes it, and shifts.
#!/bin/bash
verbose=false
file=""
while [ $# -gt 0 ]; do
case "$1" in
-v|--verbose)
verbose=true
shift
;;
-f|--file)
file="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [-v] [-f file]"
exit 0
;;
--)
shift
break
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
break
;;
esac
done
echo "Verbose: $verbose"
echo "File: $file"
echo "Remaining: $@"
The -- marker terminates option parsing. Any arguments after -- are treated as positional arguments, even if they start with -. The -* pattern catches any unrecognized option and exits with an error. The *) pattern breaks the loop when the first non-option argument is encountered.
The shift 2 for the -f option consumes both the flag and its value. The shift for the -v option consumes only the flag. The shift for the -- marker consumes the marker itself.
c. Validation and the help flag
The script must validate the values it receives. The -f option that expects a filename must check that the file exists. The -n option that expects a number must check that the value is numeric. The validation is the script’s responsibility, not getopts.
#!/bin/bash
file=""
count=0
while getopts "f:n:h" opt; do
case "$opt" in
f) file="$OPTARG" ;;
n) count="$OPTARG" ;;
h)
echo "Usage: $0 [-f file] [-n count] [-h]"
exit 0
;;
\?) echo "Invalid option" >&2; exit 1 ;;
esac
done
# Validate the file
if [ -n "$file" ] && [ ! -f "$file" ]; then
echo "Error: file not found: $file" >&2
exit 1
fi
# Validate the count
if ! [[ "$count" =~ ^[0-9]+$ ]]; then
echo "Error: count must be a number" >&2
exit 1
fi
echo "File: $file"
echo "Count: $count"
The file validation checks that the file exists. The count validation uses a regex to check that the value is numeric. The -h option prints the usage and exits before the validation.
The usage message should be printed to stdout for the -h flag (a successful exit) and to stderr for an error (a non-zero exit). The >&2 redirect sends the error message to stderr.
Complete Example Session
# ============================================
# PART 1: BASIC GETOPTS
# ============================================
cat > basic.sh <<'EOF'
#!/bin/bash
while getopts "vf:" opt; do
case "$opt" in
v) echo "Verbose mode" ;;
f) echo "File: $OPTARG" ;;
esac
done
EOF
chmod +x basic.sh
./basic.sh -v -f myfile.txt
# Output:
# Verbose mode
# File: myfile.txt
# ============================================
# PART 2: INVALID OPTION
# ============================================
./basic.sh -x
# Output: ./basic.sh: illegal option -- x
# ============================================
# PART 3: MISSING ARGUMENT
# ============================================
./basic.sh -f
# Output: ./basic.sh: option requires an argument -- f
# ============================================
# PART 4: SILENT MODE
# ============================================
cat > silent.sh <<'EOF'
#!/bin/bash
while getopts ":vf:" opt; do
case "$opt" in
v) echo "Verbose" ;;
f) echo "File: $OPTARG" ;;
\?) echo "Invalid: -$OPTARG" ;;
:) echo "Missing: -$OPTARG" ;;
esac
done
EOF
chmod +x silent.sh
./silent.sh -x
# Output: Invalid: -x
# ============================================
# PART 5: REMAINING ARGUMENTS
# ============================================
cat > remaining.sh <<'EOF'
#!/bin/bash
while getopts "v" opt; do
case "$opt" in
v) echo "Verbose" ;;
esac
done
shift $((OPTIND - 1))
echo "Remaining: $@"
EOF
chmod +x remaining.sh
./remaining.sh -v file1 file2
# Output:
# Verbose
# Remaining: file1 file2
# ============================================
# PART 6: LONG OPTIONS MANUAL
# ============================================
cat > long.sh <<'EOF'
#!/bin/bash
verbose=false
file=""
while [ $# -gt 0 ]; do
case "$1" in
-v|--verbose) verbose=true; shift ;;
-f|--file) file="$2"; shift 2 ;;
-h|--help) echo "Usage: $0 [-v] [-f file]"; exit 0 ;;
--) shift; break ;;
-*) echo "Unknown: $1" >&2; exit 1 ;;
*) break ;;
esac
done
echo "Verbose: $verbose"
echo "File: $file"
EOF
chmod +x long.sh
./long.sh --verbose --file myfile.txt
# Output:
# Verbose: true
# File: myfile.txt
# ============================================
# PART 7: FILE VALIDATION
# ============================================
cat > validate.sh <<'EOF'
#!/bin/bash
file=""
while getopts "f:" opt; do
case "$opt" in
f) file="$OPTARG" ;;
esac
done
if [ -n "$file" ] && [ ! -f "$file" ]; then
echo "File not found: $file" >&2
exit 1
fi
echo "OK"
EOF
chmod +x validate.sh
./validate.sh -f /nonexistent
# Output: File not found: /nonexistent
# ============================================
# PART 8: NUMERIC VALIDATION
# ============================================
cat > numeric.sh <<'EOF'
#!/bin/bash
count=0
while getopts "n:" opt; do
case "$opt" in
n) count="$OPTARG" ;;
esac
done
if ! [[ "$count" =~ ^[0-9]+$ ]]; then
echo "Count must be numeric" >&2
exit 1
fi
echo "Count: $count"
EOF
chmod +x numeric.sh
./numeric.sh -n abc
# Output: Count must be numeric
# ============================================
# PART 9: HELP FLAG
# ============================================
cat > help.sh <<'EOF'
#!/bin/bash
while getopts "h" opt; do
case "$opt" in
h) echo "Usage: $0 [-h]"; exit 0 ;;
esac
done
EOF
chmod +x help.sh
./help.sh -h
# Output: Usage: ./help.sh [-h]
The nine parts trace the argument parsing: basic getopts, invalid option, missing argument, silent mode, remaining arguments, long options, file validation, numeric validation, and help flag.
Quick Reference
getopts Syntax
| Component | Purpose |
|---|---|
"vf:" | Option string |
v | Flag (no argument) |
f: | Option with argument |
opt | Variable that receives the letter |
$OPTARG | Argument value |
$OPTIND | Next argument index |
Option String Modifiers
| Modifier | Effect |
|---|---|
Leading : | Silent error handling |
Trailing : | Option requires argument |
No : | Flag only |
Manual Long Option Pattern
| Pattern | Effect |
|---|---|
-v|--verbose | Both forms |
shift | Consume one |
shift 2 | Consume two |
-- | Terminate parsing |
-* | Unknown short option |
*) | First non-option |
Validation
| Check | Method |
|---|---|
| File exists | [ -f "$file" ] |
| Numeric | [[ "$n" =~ ^[0-9]+$ ]] |
| Non-empty | [ -n "$var" ] |
Best Practices
โ Do This:
while getopts "vf:" opt; do
<!-- Use getopts for short options --> # โ
shift $((OPTIND - 1))
<!-- Remove parsed options before accessing args --> # โ
if ! [[ "$count" =~ ^[0-9]+$ ]]; then
<!-- Validate numeric input --> # โ
โ Don’t Do This:
# Use getopts for long options
<!-- getopts does not support --verbose --> # โ
# Assume $OPTARG is valid
<!-- Validate before use --> # โ
# Forget to shift after getopts
<!-- Remaining args include parsed options --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Long options ignored | getopts only handles short | Manual loop |
$OPTARG empty | Missing argument | Check with -z |
| Unknown option error | No \? case | Add the case |
| Remaining args wrong | No shift | shift $((OPTIND - 1)) |
| File not validated | No check | [ -f "$file" ] |
Real-World Examples
1. Basic getopts
while getopts "vf:" opt; do case "$opt" in v) V=1;; f) F="$OPTARG";; esac; done
2. Silent Mode
while getopts ":vf:" opt; do case "$opt" in :) echo "Missing -$OPTARG";; esac; done
3. Remaining Arguments
shift $((OPTIND - 1))
echo "$@"
4. Long Options
while [ $# -gt 0 ]; do case "$1" in --verbose) V=1; shift;; esac; done
5. -- Terminator
case "$1" in --) shift; break;; esac
6. Unknown Option
case "$1" in -*) echo "Unknown"; exit 1;; esac
7. File Validation
if [ ! -f "$file" ]; then exit 1; fi
8. Numeric Validation
if ! [[ "$n" =~ ^[0-9]+$ ]]; then exit 1; fi
9. Help Flag
case "$opt" in h) echo "Usage: ..."; exit 0;; esac
10. Combined Flags
./script.sh -vf file
Visual
getopts Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ while getopts "vf:" opt โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ $opt = "v" โ case "$opt" in v) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ $opt = "f" โ $OPTARG = "file" โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ $opt = "?" โ unknown option โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ getopts returns false โ loop ends โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
OPTIND Shift
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ./script.sh -v -f file arg1 arg2 โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ After getopts loop: $OPTIND = 5 โ
โ (consumed -v, -f, file = 3 args + 1) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ shift $((OPTIND - 1))
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ $@ = arg1 arg2 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Short option parser | getopts |
| Option string | "vf:" |
| Flag | Letter without colon |
| Option with arg | Letter followed by colon |
| Argument value | $OPTARG |
| Next index | $OPTIND |
| Remaining args | shift $((OPTIND - 1)) |
| Long options | Manual while/case |
| Terminator | -- |
Key takeaways:
getoptshandles short options. The option string declares which letters are flags and which take arguments. The$OPTARGholds the argument value. The$OPTINDtracks the position.- Long options require a manual loop.
getoptsdoes not support--verbose. Thewhile/case/shiftpattern handles both short and long options. - The
--terminator ends option parsing. Any argument after--is treated as a positional argument, even if it starts with-. - Validation is the script’s responsibility.
getoptsextracts the value; the script must check that the value is valid. The file existence check and the numeric check are the standard validations. - The
-hflag is the standard help. The script prints the usage and exits with0. The usage message should be concise and list the options.
Remember: The getopts builtin is the POSIX tool for short options. The manual loop is the pattern for long options. The $OPTARG is the value. The $OPTIND is the position. The shift is the cleanup. The case is the dispatch. The exam tests the syntax and the distinction between short and long options.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!