Command Line Parsing: Script Arguments and Options Linux Mastery Series
Prerequisites
What is Command Line Parsing in Bash?
Command line parsing is the process of extracting and validating arguments, options, and flags passed to a bash script during execution. Instead of hardcoding values, your scripts can accept dynamic inputs like ./script.sh -f input.txt -v --output results/
, making them flexible and reusable across different scenarios.
Quick Win Example:
#!/bin/bash
# Simple argument parser
while [[ $# -gt 0 ]]; do
case $1 in
-f|--file)
FILE="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
echo "Processing file: $FILE"
[[ $VERBOSE ]] && echo "Verbose mode enabled"
This immediately gives you a professional argument parser that handles both short (-f
) and long (--file
) options with validation.
Table of Contents
- How Does Command Line Argument Parsing Work in Bash?
- What Are Positional Parameters in Shell Scripts?
- How to Use getopts for Option Parsing?
- What is the Difference Between $@ and $*?
- How to Parse Long Options in Bash Scripts?
- Why Should You Validate Script Arguments?
- Best Practices for Bash Argument Handling
- Frequently Asked Questions
- Troubleshooting Common Parsing Issues
How Does Command Line Argument Parsing Work in Bash?
Bash provides multiple mechanisms for handling script inputs through special variables and built-in commands. Consequently, understanding these tools enables you to create robust, user-friendly scripts that behave like professional command-line utilities.
Special Variables for Argument Access
Bash automatically populates several variables when your script runs:
Variable | Description | Example Value |
---|---|---|
$0 | Script name | ./backup.sh |
$1 –$9 | Positional arguments 1-9 | $1 = first argument |
${10} | Arguments beyond 9 (braces required) | ${10} = tenth argument |
$# | Total argument count | 3 (for 3 arguments) |
$@ | All arguments as separate words | "arg1" "arg2" "arg3" |
$* | All arguments as single string | "arg1 arg2 arg3" |
$? | Exit status of last command | 0 (success) or error code |
Practical Example:
#!/bin/bash
# demonstrate-args.sh
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
echo "All arguments: $@"
# Check if arguments provided
if [ $# -eq 0 ]; then
echo "Error: No arguments provided"
echo "Usage: $0 <arg1> <arg2>"
exit 1
fi
Test it:
chmod +x demonstrate-args.sh
./demonstrate-args.sh hello world 123
Output:
Script name: ./demonstrate-args.sh
First argument: hello
Second argument: world
Total arguments: 3
All arguments: hello world 123
Moreover, the Linux Terminal Basics guide covers fundamental command execution concepts that complement argument parsing.
What Are Positional Parameters in Shell Scripts?
Positional parameters are arguments passed to your script based on their position in the command line. Therefore, $1
always represents the first argument, $2
the second, and so forth.
Basic Positional Parameter Usage
#!/bin/bash
# backup-file.sh - Simple backup utility
SOURCE="$1"
DESTINATION="$2"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Validate required parameters
if [ -z "$SOURCE" ] || [ -z "$DESTINATION" ]; then
echo "Usage: $0 <source-file> <backup-directory>"
exit 1
fi
# Create backup with timestamp
cp "$SOURCE" "${DESTINATION}/$(basename $SOURCE)_${TIMESTAMP}"
echo "Backup created: ${DESTINATION}/$(basename $SOURCE)_${TIMESTAMP}"
Execute:
./backup-file.sh /etc/hosts /backup/configs/
Shifting Arguments with the shift Command
The shift
command removes the first positional parameter, shifting all others down by one position. Consequently, this technique is essential for processing variable-length argument lists.
#!/bin/bash
# process-files.sh - Process multiple files
while [ $# -gt 0 ]; do
echo "Processing: $1"
# Your file processing logic here
wc -l "$1"
# Shift to next argument
shift
done
Usage:
./process-files.sh file1.txt file2.txt file3.txt
Additionally, file operations mastery explains how to manipulate files effectively once you’ve parsed their paths.
How to Use getopts for Option Parsing?
The getopts
built-in command provides POSIX-compliant option parsing, making it the preferred method for handling short options (-a
, -b
, -c
) with or without arguments.
getopts Syntax and Usage
Syntax:
getopts optstring variable_name
optstring
: Options your script accepts (e.g.,"abc:"
= options a, b, and c where c requires an argument):
after a letter means that option requires an argumentvariable_name
: Variable to store the current option
Complete getopts Example
#!/bin/bash
# advanced-parser.sh
# Default values
VERBOSE=false
OUTPUT_FILE=""
MODE="normal"
# Display help
show_help() {
cat << EOF
Usage: ${0##*/} [OPTIONS]
OPTIONS:
-h Display this help message
-v Enable verbose output
-o FILE Specify output file
-m MODE Set operation mode (normal|debug|quiet)
EXAMPLES:
${0##*/} -v -o results.txt -m debug
${0##*/} -o output.log
EOF
}
# Parse options
while getopts "hvo:m:" opt; do
case $opt in
h)
show_help
exit 0
;;
v)
VERBOSE=true
;;
o)
OUTPUT_FILE="$OPTARG"
;;
m)
MODE="$OPTARG"
# Validate mode
if [[ ! "$MODE" =~ ^(normal|debug|quiet)$ ]]; then
echo "Error: Invalid mode '$MODE'"
echo "Valid modes: normal, debug, quiet"
exit 1
fi
;;
\?)
echo "Invalid option: -$OPTARG" >&2
show_help
exit 1
;;
:)
echo "Option -$OPTARG requires an argument" >&2
exit 1
;;
esac
done
# Shift processed options
shift $((OPTIND-1))
# Remaining positional arguments in $@
echo "Configuration:"
echo " Verbose: $VERBOSE"
echo " Output: ${OUTPUT_FILE:-stdout}"
echo " Mode: $MODE"
echo " Remaining args: $@"
Test various scenarios:
# Show help
./advanced-parser.sh -h
# Enable verbose with output file
./advanced-parser.sh -v -o results.txt
# Set mode with remaining arguments
./advanced-parser.sh -m debug file1 file2
# Invalid option error
./advanced-parser.sh -x
Furthermore, the Bash Scripting Basics guide provides foundational knowledge for building sophisticated parsers.
getopts Option String Patterns
Pattern | Meaning | Example |
---|---|---|
"abc" | Options -a, -b, -c without arguments | -a -b -c |
"a:b:c" | All three require arguments | -a val1 -b val2 |
"a:bc" | Only -a requires argument | -a value -b -c |
":abc" | Silent error mode (prefix with π | Suppress automatic errors |
According to the GNU Bash Manual, getopts
handles option combinations efficiently while maintaining POSIX compliance.
What is the Difference Between $@ and $*?
Both $@
and $*
represent all positional parameters, but they behave differently when quoted. Therefore, understanding this distinction is critical for correct argument handling.
Behavior Comparison
#!/bin/bash
# demonstrate-special-vars.sh
echo "Arguments passed: arg1 'arg 2 with spaces' arg3"
echo ""
echo "Using unquoted \$@:"
for arg in $@; do
echo " [$arg]"
done
echo ""
echo "Using quoted \"\$@\":"
for arg in "$@"; do
echo " [$arg]"
done
echo ""
echo "Using quoted \"\$*\":"
for arg in "$*"; do
echo " [$arg]"
done
Execute:
./demonstrate-special-vars.sh arg1 "arg 2 with spaces" arg3
Output:
Using unquoted $@:
[arg1]
[arg] [2]
[with]
[spaces]
[arg3]
Using quoted “$@”:
[arg1]
[arg 2 with spaces]
[arg3]
Using quoted “$*”:
[arg1 arg 2 with spaces arg3]
Best Practice Table
Scenario | Use | Reason |
---|---|---|
Passing args to another command | "$@" | Preserves individual arguments |
Building a single string | "$*" | Combines all into one string |
Loop through arguments | "$@" | Iterates correctly over each arg |
Logging all arguments | "$*" | Creates readable log entry |
Recommended Pattern:
#!/bin/bash
# Always use quoted "$@" for argument forwarding
# Good: Preserves argument integrity
process_files() {
for file in "$@"; do
echo "Processing: $file"
done
}
process_files "$@"
As documented in the Advanced Bash-Scripting Guide, always quote $@
to preserve argument separation.
How to Parse Long Options in Bash Scripts?
While getopts
only handles short options, manual parsing with case
statements enables support for GNU-style long options (--help
, --verbose
, --output=file
). Consequently, this approach provides maximum flexibility.
Manual Long Option Parser
#!/bin/bash
# long-options-parser.sh
# Initialize variables
VERBOSE=false
OUTPUT=""
CONFIG_FILE=""
DRY_RUN=false
# Help function
print_usage() {
cat << 'EOF'
Usage: script.sh [OPTIONS]
OPTIONS:
-h, --help Show this help message
-v, --verbose Enable verbose output
-o, --output FILE Specify output file
-c, --config FILE Use configuration file
-n, --dry-run Perform dry run without changes
--version Show version information
EXAMPLES:
script.sh --verbose --output results.txt
script.sh -v -o results.txt --config app.conf
script.sh --dry-run --verbose
EOF
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
print_usage
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output)
if [[ -z "$2" ]] || [[ "$2" == -* ]]; then
echo "Error: --output requires a file path"
exit 1
fi
OUTPUT="$2"
shift 2
;;
-c|--config)
if [[ -z "$2" ]] || [[ "$2" == -* ]]; then
echo "Error: --config requires a file path"
exit 1
fi
CONFIG_FILE="$2"
shift 2
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
--version)
echo "Version 1.0.0"
exit 0
;;
--output=*)
# Handle --output=file syntax
OUTPUT="${1#*=}"
shift
;;
--config=*)
CONFIG_FILE="${1#*=}"
shift
;;
-*)
echo "Error: Unknown option $1"
print_usage
exit 1
;;
*)
# Positional argument
echo "Processing positional argument: $1"
shift
;;
esac
done
# Display parsed configuration
echo "Configuration:"
echo " Verbose: $VERBOSE"
echo " Output: ${OUTPUT:-<stdout>}"
echo " Config: ${CONFIG_FILE:-<none>}"
echo " Dry Run: $DRY_RUN"
Test comprehensive scenarios:
# Long options with spaces
./long-options-parser.sh --verbose --output results.txt
# Long options with equals syntax
./long-options-parser.sh --output=data.log --config=app.conf
# Mixed short and long options
./long-options-parser.sh -v --dry-run -o file.txt
# Show help
./long-options-parser.sh --help
Hybrid Approach: Combining getopts and Manual Parsing
#!/bin/bash
# hybrid-parser.sh
parse_long_options() {
for arg in "$@"; do
case "$arg" in
--help)
echo "--help flag detected"
exit 0
;;
--verbose)
VERBOSE=true
;;
--output=*)
OUTPUT="${arg#*=}"
;;
esac
done
}
# First pass: handle long options
parse_long_options "$@"
# Second pass: handle short options with getopts
while getopts "hvo:" opt; do
case $opt in
h) echo "Help requested"; exit 0 ;;
v) VERBOSE=true ;;
o) OUTPUT="$OPTARG" ;;
esac
done
Similarly, understanding error handling in bash scripts ensures your parsers gracefully manage invalid inputs.
Why Should You Validate Script Arguments?
Argument validation prevents runtime errors, security vulnerabilities, and data corruption. Therefore, implementing robust validation is not optionalβit’s a professional requirement.
Essential Validation Checks
#!/bin/bash
# robust-validator.sh
validate_arguments() {
local file="$1"
local mode="$2"
local count="$3"
# Check required arguments
if [ -z "$file" ]; then
echo "Error: File argument required" >&2
return 1
fi
# Validate file exists
if [ ! -f "$file" ]; then
echo "Error: File '$file' does not exist" >&2
return 1
fi
# Validate file is readable
if [ ! -r "$file" ]; then
echo "Error: File '$file' is not readable" >&2
return 1
fi
# Validate mode with whitelist
case "$mode" in
read|write|append)
: # Valid mode
;;
*)
echo "Error: Invalid mode '$mode'" >&2
echo "Valid modes: read, write, append" >&2
return 1
;;
esac
# Validate numeric argument
if ! [[ "$count" =~ ^[0-9]+$ ]]; then
echo "Error: Count must be a positive integer" >&2
return 1
fi
# Range validation
if [ "$count" -lt 1 ] || [ "$count" -gt 1000 ]; then
echo "Error: Count must be between 1 and 1000" >&2
return 1
fi
return 0
}
# Example usage
FILE="$1"
MODE="$2"
COUNT="$3"
if validate_arguments "$FILE" "$MODE" "$COUNT"; then
echo "Validation passed!"
echo "Processing $FILE in $MODE mode, count: $COUNT"
else
echo "Validation failed"
exit 1
fi
Security-Focused Validation
#!/bin/bash
# secure-input-validation.sh
# Sanitize user input to prevent injection
sanitize_input() {
local input="$1"
# Remove potentially dangerous characters
echo "$input" | sed 's/[;&|`$]//g'
}
# Validate path doesn't escape intended directory
validate_safe_path() {
local path="$1"
local base_dir="$2"
# Resolve absolute path
local abs_path=$(readlink -f "$path")
local abs_base=$(readlink -f "$base_dir")
# Check if path is within base directory
if [[ "$abs_path" != "$abs_base"* ]]; then
echo "Error: Path escapes base directory" >&2
return 1
fi
return 0
}
# Validate email format
validate_email() {
local email="$1"
local email_regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if [[ ! "$email" =~ $email_regex ]]; then
echo "Error: Invalid email format" >&2
return 1
fi
return 0
}
# Example: Validate IP address
validate_ip() {
local ip="$1"
local ip_regex="^([0-9]{1,3}\.){3}[0-9]{1,3}$"
if [[ ! "$ip" =~ $ip_regex ]]; then
echo "Error: Invalid IP format" >&2
return 1
fi
# Validate each octet is 0-255
IFS='.' read -ra OCTETS <<< "$ip"
for octet in "${OCTETS[@]}"; do
if [ "$octet" -gt 255 ]; then
echo "Error: Invalid IP address" >&2
return 1
fi
done
return 0
}
The OWASP Secure Coding Practices emphasize input validation as the first line of defense against security vulnerabilities.
Best Practices for Bash Argument Handling
Professional scripts follow established conventions that improve usability, maintainability, and reliability. Therefore, adopting these practices elevates your scripts from amateur to production-ready.
1. Provide Comprehensive Help Documentation
#!/bin/bash
# professional-help.sh
show_help() {
cat << 'EOF'
NAME
professional-script - Example of professional help documentation
SYNOPSIS
professional-script [OPTIONS] <input-file>
DESCRIPTION
This script demonstrates comprehensive help documentation with
detailed explanations, examples, and exit codes.
OPTIONS
-h, --help
Display this help message and exit
-v, --verbose
Enable verbose output for debugging
-o, --output FILE
Specify output file (default: stdout)
-f, --format FORMAT
Output format: json, xml, csv (default: json)
-q, --quiet
Suppress all output except errors
EXAMPLES
# Process file with verbose output
professional-script -v input.txt
# Specify custom output and format
professional-script -o results.xml -f xml data.txt
# Quiet mode with custom format
professional-script --quiet --format csv data.txt
EXIT STATUS
0 Success
1 General error
2 Invalid arguments
3 File not found
4 Permission denied
AUTHOR
LinuxTips.pro
SEE ALSO
Related documentation: https://linuxtips.pro/bash-scripting-basics
EOF
}
2. Use Consistent Option Naming Conventions
Convention | Example | Description |
---|---|---|
Short options | -h , -v , -o | Single dash, single character |
Long options | --help , --verbose | Double dash, full word |
Options with values | -o file or -o=file | Space or equals separator |
Boolean flags | -v , --verbose | No value required |
Standard options | -h (help), -v (version) | Follow POSIX conventions |
3. Implement Default Values and Configuration Files
#!/bin/bash
# config-defaults.sh
# Default configuration
DEFAULT_CONFIG="/etc/myapp/config.conf"
CONFIG_FILE="${CONFIG_FILE:-$DEFAULT_CONFIG}"
VERBOSE="${VERBOSE:-false}"
OUTPUT_DIR="${OUTPUT_DIR:-/tmp/output}"
MAX_RETRIES="${MAX_RETRIES:-3}"
# Load configuration file if exists
load_config() {
local config_file="$1"
if [ -f "$config_file" ]; then
echo "Loading configuration from $config_file"
# Source config file in subshell for safety
source "$config_file"
else
echo "Config file not found, using defaults"
fi
}
# Command line arguments override config file
parse_args() {
while getopts "c:v:o:r:" opt; do
case $opt in
c) CONFIG_FILE="$OPTARG" ;;
v) VERBOSE="$OPTARG" ;;
o) OUTPUT_DIR="$OPTARG" ;;
r) MAX_RETRIES="$OPTARG" ;;
esac
done
}
# Priority: CLI args > Config file > Defaults
load_config "$CONFIG_FILE"
parse_args "$@"
echo "Final configuration:"
echo " Verbose: $VERBOSE"
echo " Output: $OUTPUT_DIR"
echo " Retries: $MAX_RETRIES"
4. Implement Proper Error Handling and Exit Codes
#!/bin/bash
# error-handling-parser.sh
# Exit codes
readonly E_SUCCESS=0
readonly E_GENERAL=1
readonly E_INVALID_ARGS=2
readonly E_FILE_NOT_FOUND=3
readonly E_PERMISSION_DENIED=4
# Error handler
error_exit() {
local message="$1"
local exit_code="${2:-$E_GENERAL}"
echo "ERROR: $message" >&2
exit "$exit_code"
}
# Parse with error handling
INPUT_FILE="$1"
[ -z "$INPUT_FILE" ] && error_exit "Input file required" $E_INVALID_ARGS
[ ! -e "$INPUT_FILE" ] && error_exit "File not found: $INPUT_FILE" $E_FILE_NOT_FOUND
[ ! -r "$INPUT_FILE" ] && error_exit "Permission denied: $INPUT_FILE" $E_PERMISSION_DENIED
echo "Processing $INPUT_FILE"
exit $E_SUCCESS
5. Create Reusable Parsing Functions
#!/bin/bash
# reusable-parser-lib.sh
# Reusable parsing library
# Check if argument is an option
is_option() {
[[ "$1" == -* ]]
}
# Get value for option, handling both -o value and -o=value
get_option_value() {
local opt="$1"
local next="$2"
if [[ "$opt" == *"="* ]]; then
echo "${opt#*=}"
elif ! is_option "$next"; then
echo "$next"
else
return 1
fi
}
# Validate required options are set
require_option() {
local var_name="$1"
local opt_name="$2"
local var_value="${!var_name}"
if [ -z "$var_value" ]; then
echo "Error: Required option $opt_name not provided" >&2
return 1
fi
}
# Example usage
parse_with_lib() {
local output=""
local config=""
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
output=$(get_option_value "$1" "$2") || {
echo "Error: --output requires a value" >&2
return 1
}
shift 2
;;
-c|--config)
config=$(get_option_value "$1" "$2") || {
echo "Error: --config requires a value" >&2
return 1
}
shift 2
;;
*)
shift
;;
esac
done
require_option "output" "--output" || return 1
require_option "config" "--config" || return 1
echo "Output: $output"
echo "Config: $config"
}
Additionally, the Advanced Bash Scripting guide demonstrates how to structure modular, maintainable script libraries.
Frequently Asked Questions
How do I parse arguments with spaces in values?
Always quote variables and use "$@"
instead of $@
. When passing arguments with spaces, wrap them in quotes:
#!/bin/bash
# Handle arguments with spaces
for arg in "$@"; do
echo "Argument: [$arg]"
done
# Usage:
# ./script.sh "file with spaces.txt" "another file.log"
Can I combine multiple short options like -vxf?
Yes, but you need to handle this manually or use enhanced parsing. Here’s a pattern:
#!/bin/bash
# Expand combined options
expand_options() {
local args=()
for arg in "$@"; do
if [[ "$arg" =~ ^-[a-zA-Z]{2,}$ ]]; then
# Split -vxf into -v -x -f
local opts="${arg#-}"
for ((i=0; i<${#opts}; i++)); do
args+=("-${opts:$i:1}")
done
else
args+=("$arg")
fi
done
echo "${args[@]}"
}
# Usage
EXPANDED=$(expand_options "$@")
# Process $EXPANDED with getopts
How do I make arguments optional with defaults?
Use parameter expansion with default values:
#!/bin/bash
# Default values
FILE="${1:-default.txt}"
MODE="${2:-read}"
COUNT="${3:-10}"
echo "File: $FILE (default: default.txt)"
echo "Mode: $MODE (default: read)"
echo "Count: $COUNT (default: 10)"
# Alternative: Use OR operator
OUTPUT_DIR="${OUTPUT_DIR:-/tmp}"
: "${CONFIG_FILE:=/etc/app.conf}" # Assign if unset
What’s the best way to handle flags vs. options with values?
Use separate variables and clear documentation:
#!/bin/bash
# Flags (boolean)
VERBOSE=false
DRY_RUN=false
FORCE=false
# Options (with values)
OUTPUT=""
CONFIG=""
LEVEL=""
while getopts "vnfo:c:l:" opt; do
case $opt in
v) VERBOSE=true ;;
n) DRY_RUN=true ;;
f) FORCE=true ;;
o) OUTPUT="$OPTARG" ;;
c) CONFIG="$OPTARG" ;;
l) LEVEL="$OPTARG" ;;
esac
done
How do I pass parsed arguments to another function or script?
Use "$@"
after shifting processed options:
#!/bin/bash
# Parse options
while getopts "vo:" opt; do
case $opt in
v) VERBOSE=true ;;
o) OUTPUT="$OPTARG" ;;
esac
done
shift $((OPTIND-1))
# Pass remaining args to function
process_files "$@"
# Or to another script
./other-script.sh "$@"
Should I use getopts or manual parsing?
Choose based on requirements:
- Use getopts: For POSIX-compliant short options only (
-a
,-b
) - Use manual parsing: For long options (
--help
), combined options (-vxf
), or complex logic - Use libraries: For enterprise applications, consider argbash or shflags
According to Stack Overflow’s bash community, manual parsing provides more flexibility for modern CLI applications.
Troubleshooting Common Parsing Issues
Issue: Arguments Not Being Recognized
Symptom: Options are treated as positional arguments
# Problem
./script.sh file.txt -v
# -v is treated as second positional argument instead of option
Solution: Parse options before positional arguments
#!/bin/bash
# Parse options first
while getopts "v" opt; do
case $opt in
v) VERBOSE=true ;;
esac
done
shift $((OPTIND-1))
# Now handle positional arguments
FILE="$1"
Diagnostic Commands:
# Debug argument parsing
set -x # Enable debug mode
./script.sh -v file.txt
set +x # Disable debug mode
# Check what arguments script receives
printf '%s\n' "$@"
Issue: getopts Not Finding Option Argument
Symptom: Error: “option requires an argument”
# This fails
./script.sh -o
Solution: Implement proper error handling
#!/bin/bash
while getopts "o:" opt; do
case $opt in
o)
if [ -z "$OPTARG" ]; then
echo "Error: -o requires a file path" >&2
exit 1
fi
OUTPUT="$OPTARG"
;;
:)
echo "Error: Option -$OPTARG requires an argument" >&2
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
exit 1
;;
esac
done
Issue: Spaces in Arguments Breaking Parsing
Symptom: Argument with space splits into multiple arguments
# Problem
./script.sh "file with spaces.txt"
# Received as: file, with, spaces.txt
Solution: Always quote variable expansions
#!/bin/bash
# Wrong
for arg in $@; do
echo "$arg"
done
# Correct
for arg in "$@"; do
echo "$arg"
done
# Processing files
FILE="$1"
cat "$FILE" # Not: cat $FILE
Test Script:
#!/bin/bash
# test-quoting.sh
echo "Testing argument quoting"
echo "Method 1 - Unquoted \$@:"
for arg in $@; do
echo " arg: [$arg]"
done
echo ""
echo "Method 2 - Quoted \"\$@\":"
for arg in "$@"; do
echo " arg: [$arg]"
done
# Test: ./test-quoting.sh "file 1.txt" "file 2.txt"
Issue: OPTIND Not Reset in Functions
Symptom: getopts doesn’t work in called functions
# Problem
parse_opts() {
while getopts "v" opt; do
case $opt in
v) VERBOSE=true ;;
esac
done
}
parse_opts "$@" # Doesn't work as expected
parse_opts "$@" # Second call fails
Solution: Reset OPTIND before each getopts use
#!/bin/bash
parse_opts() {
local OPTIND=1 # Reset OPTIND locally
local VERBOSE=false
while getopts "v" opt; do
case $opt in
v) VERBOSE=true ;;
esac
done
echo "Verbose: $VERBOSE"
}
# Now works correctly multiple times
parse_opts "$@"
parse_opts "$@"
Issue: Long Options Not Working with getopts
Symptom: Long options like --help
not recognized
# getopts doesn't support long options natively
./script.sh --help # Unrecognized
Solution: Use manual parsing for long options
#!/bin/bash
# Hybrid approach
for arg in "$@"; do
case "$arg" in
--help)
show_help
exit 0
;;
--verbose)
VERBOSE=true
;;
esac
done
# Then use getopts for short options
while getopts "hv" opt; do
case $opt in
h) show_help; exit 0 ;;
v) VERBOSE=true ;;
esac
done
Issue: Special Characters Breaking Validation
Symptom: Arguments with special characters cause errors
# Problem characters: ; & | ` $ ( )
./script.sh "test; rm -rf /"
Solution: Sanitize and validate all inputs
#!/bin/bash
sanitize_input() {
local input="$1"
# Remove dangerous characters
echo "$input" | sed 's/[;&|`$(){}]//g'
}
validate_filename() {
local file="$1"
# Only allow alphanumeric, dash, underscore, dot
if [[ ! "$file" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "Error: Invalid filename format" >&2
return 1
fi
}
# Usage
USER_INPUT="$1"
SAFE_INPUT=$(sanitize_input "$USER_INPUT")
validate_filename "$SAFE_INPUT" || exit 1
Additional Diagnostic Tools:
# Check argument parsing step-by-step
bash -x ./script.sh -v -o file.txt
# Verify special variables
printf 'Number of args: %d\n' "$#"
printf 'Script name: %s\n' "$0"
printf 'All args: %s\n' "$*"
printf 'Each arg:\n'
printf ' [%s]\n' "$@"
# Test quoting behavior
set -- "arg 1" "arg 2" "arg 3"
echo "Test arguments set"
for arg in "$@"; do
echo "Arg: [$arg]"
done
The Linux Command Library provides additional resources for debugging bash scripts and understanding shell behavior.
Real-World Use Cases
Production Backup Script
#!/bin/bash
# production-backup.sh - Enterprise backup solution
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Configuration
BACKUP_ROOT="/backup"
LOG_FILE="/var/log/backup.log"
RETENTION_DAYS=7
COMPRESSION="gzip"
VERBOSE=false
DRY_RUN=false
log() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" | tee -a "$LOG_FILE"
}
show_help() {
cat << 'EOF'
Production Backup Script
USAGE:
production-backup.sh [OPTIONS] <source-directory>
OPTIONS:
-h, --help Show this help
-v, --verbose Enable verbose output
-n, --dry-run Simulate backup without creating files
-d, --destination DIR Backup destination (default: /backup)
-r, --retention DAYS Keep backups for N days (default: 7)
-c, --compression TYPE Compression: gzip, bzip2, xz (default: gzip)
-e, --exclude PATTERN Exclude pattern (can be specified multiple times)
EXAMPLES:
production-backup.sh /var/www/html
production-backup.sh -v -d /mnt/backup -r 30 /home
production-backup.sh --dry-run --exclude "*.log" /etc
EOF
}
# Parse arguments
EXCLUDE_PATTERNS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-d|--destination)
BACKUP_ROOT="$2"
shift 2
;;
-r|--retention)
RETENTION_DAYS="$2"
shift 2
;;
-c|--compression)
COMPRESSION="$2"
shift 2
;;
-e|--exclude)
EXCLUDE_PATTERNS+=("$2")
shift 2
;;
-*)
echo "Error: Unknown option $1" >&2
show_help
exit 1
;;
*)
SOURCE_DIR="$1"
shift
;;
esac
done
# Validate required arguments
if [ -z "${SOURCE_DIR:-}" ]; then
echo "Error: Source directory required" >&2
show_help
exit 2
fi
if [ ! -d "$SOURCE_DIR" ]; then
log "ERROR: Source directory does not exist: $SOURCE_DIR"
exit 3
fi
# Create backup
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$(basename $SOURCE_DIR)_${TIMESTAMP}.tar.gz"
BACKUP_PATH="${BACKUP_ROOT}/${BACKUP_NAME}"
log "Starting backup of $SOURCE_DIR"
[[ $VERBOSE == true ]] && log "Backup will be saved to: $BACKUP_PATH"
if [[ $DRY_RUN == true ]]; then
log "DRY RUN: Would create backup at $BACKUP_PATH"
else
mkdir -p "$BACKUP_ROOT"
# Build tar command with exclusions
TAR_CMD="tar -czf \"$BACKUP_PATH\" -C \"$(dirname $SOURCE_DIR)\" \"$(basename $SOURCE_DIR)\""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
TAR_CMD+=" --exclude=\"$pattern\""
done
eval $TAR_CMD
log "Backup completed successfully"
fi
# Cleanup old backups
find "$BACKUP_ROOT" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "Removed backups older than $RETENTION_DAYS days"
System Health Monitor
#!/bin/bash
# system-monitor.sh - Comprehensive system health checker
# Default thresholds
CPU_THRESHOLD=80
MEMORY_THRESHOLD=90
DISK_THRESHOLD=85
EMAIL_ALERT=""
SLACK_WEBHOOK=""
CHECK_INTERVAL=300
parse_arguments() {
while getopts "c:m:d:e:s:i:h" opt; do
case $opt in
c) CPU_THRESHOLD="$OPTARG" ;;
m) MEMORY_THRESHOLD="$OPTARG" ;;
d) DISK_THRESHOLD="$OPTARG" ;;
e) EMAIL_ALERT="$OPTARG" ;;
s) SLACK_WEBHOOK="$OPTARG" ;;
i) CHECK_INTERVAL="$OPTARG" ;;
h)
echo "Usage: $0 [-c cpu%] [-m mem%] [-d disk%] [-e email] [-s slack] [-i interval]"
exit 0
;;
esac
done
}
check_cpu() {
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if (( $(echo "$cpu_usage > $CPU_THRESHOLD" | bc -l) )); then
echo "WARNING: CPU usage at ${cpu_usage}%"
return 1
fi
return 0
}
check_memory() {
local mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}')
if [ "$mem_usage" -gt "$MEMORY_THRESHOLD" ]; then
echo "WARNING: Memory usage at ${mem_usage}%"
return 1
fi
return 0
}
parse_arguments "$@"
while true; do
check_cpu
check_memory
sleep "$CHECK_INTERVAL"
done
Additional Resources
Official Documentation
- GNU Bash Manual – Complete bash reference including command line argument handling
- POSIX Shell Command Language – Standard shell scripting specifications
- Advanced Bash-Scripting Guide – Comprehensive bash scripting tutorial
Command References
- Man7.org – Bash Manual – Online manual pages
- ExplainShell – Interactive command breakdown tool
- ShellCheck – Shell script analysis tool
Related LinuxTips.pro Guides
- Bash Scripting Basics: Your First Scripts – Foundation for script development
- Advanced Bash Scripting: Functions and Arrays – Building modular scripts
- Error Handling in Bash Scripts – Robust error management
- Regular Expressions in Linux: Complete Guide – Pattern matching for input validation
- Linux Terminal Basics: Your First Commands – Essential command line skills
Community Resources
- Stack Overflow – Bash Tag – Community Q&A
- r/bash – Reddit community discussions
- Bash Hackers Wiki – Collaborative bash documentation
Conclusion
Mastering command line parsing transforms your bash scripts from simple automation tools into professional command-line applications. By implementing proper argument handling with getopts
, manual parsing for long options, comprehensive validation, and robust error handling, you create scripts that are secure, maintainable, and user-friendly.
Furthermore, following established conventions like providing help documentation, supporting both short and long options, and implementing sensible defaults ensures your scripts integrate seamlessly into any Linux environment. Whether you’re building system administration tools, deployment scripts, or complex automation workflows, the techniques covered in this guide provide the foundation for production-ready bash scripting.
Remember to always validate inputs, handle errors gracefully, and document your script’s interface thoroughly. These practices not only prevent security vulnerabilities and runtime errors but also make your scripts more accessible to other users and easier to maintain over time.
Start implementing these command line parsing patterns in your next script, and you’ll immediately notice improved reliability and professional polish in your bash automation toolkit.
Last Updated: October 2025