How to Pass Arguments to Shell Scripts
Passing arguments to shell scripts is a fundamental skill that transforms static scripts into dynamic, reusable tools. Whether you're automating system administration tasks, processing files, or building complex workflows, understanding how to effectively handle command-line arguments is essential for creating professional-grade shell scripts.
This comprehensive guide will teach you everything you need to know about passing and handling arguments in shell scripts, from basic positional parameters to advanced argument parsing techniques. You'll learn how to make your scripts more flexible, user-friendly, and robust through proper argument handling.
Table of Contents
1. [Prerequisites and Requirements](#prerequisites-and-requirements)
2. [Understanding Positional Parameters](#understanding-positional-parameters)
3. [Special Variables for Argument Handling](#special-variables-for-argument-handling)
4. [Basic Argument Passing Examples](#basic-argument-passing-examples)
5. [Advanced Argument Handling Techniques](#advanced-argument-handling-techniques)
6. [Validating and Processing Arguments](#validating-and-processing-arguments)
7. [Named Arguments and Options](#named-arguments-and-options)
8. [Working with Variable Number of Arguments](#working-with-variable-number-of-arguments)
9. [Common Issues and Troubleshooting](#common-issues-and-troubleshooting)
10. [Best Practices and Professional Tips](#best-practices-and-professional-tips)
11. [Real-World Examples and Use Cases](#real-world-examples-and-use-cases)
12. [Conclusion](#conclusion)
Prerequisites and Requirements
Before diving into argument passing techniques, ensure you have:
- Basic understanding of shell scripting concepts
- Access to a Unix-like system (Linux, macOS, or WSL on Windows)
- A text editor for writing scripts
- Basic knowledge of command-line operations
- Familiarity with file permissions and script execution
Required Tools
- Bash shell (version 4.0 or later recommended)
- Text editor (vim, nano, VS Code, or similar)
- Terminal access
- Basic understanding of variables and control structures
Understanding Positional Parameters
Positional parameters are the foundation of argument passing in shell scripts. These are special variables that automatically store the arguments passed to your script when it's executed.
Basic Positional Parameters
When you run a script with arguments, the shell automatically assigns them to numbered variables:
- `$0` - The name of the script itself
- `$1` - The first argument
- `$2` - The second argument
- `$3` - The third argument
- And so on...
Here's a simple example to demonstrate positional parameters:
```bash
#!/bin/bash
File: basic_args.sh
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Third argument: $3"
```
To use this script:
```bash
chmod +x basic_args.sh
./basic_args.sh hello world 123
```
Output:
```
Script name: ./basic_args.sh
First argument: hello
Second argument: world
Third argument: 123
```
Accessing Arguments Beyond $9
For arguments beyond the ninth position, you need to use curly braces:
```bash
#!/bin/bash
File: many_args.sh
echo "Tenth argument: ${10}"
echo "Eleventh argument: ${11}"
echo "Twelfth argument: ${12}"
```
Special Variables for Argument Handling
Shell scripts provide several special variables that make argument handling more powerful and flexible:
Essential Special Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `$#` | Number of arguments passed | `echo "You passed $# arguments"` |
| `$` | All arguments as a single string | `echo "All args: $"` |
| `$@` | All arguments as separate strings | `for arg in "$@"; do echo $arg; done` |
| `$$` | Process ID of the current shell | `echo "PID: $$"` |
| `$?` | Exit status of the last command | `echo "Last exit code: $?"` |
Practical Example with Special Variables
```bash
#!/bin/bash
File: special_vars.sh
echo "=== Script Information ==="
echo "Script name: $0"
echo "Process ID: $$"
echo "Number of arguments: $#"
echo
echo "=== All Arguments (using \$*) ==="
echo "Arguments as single string: $*"
echo
echo "=== All Arguments (using \$@) ==="
echo "Arguments as separate items:"
for arg in "$@"; do
echo " - $arg"
done
```
Running this script:
```bash
./special_vars.sh apple banana "cherry pie" 123
```
Output:
```
=== Script Information ===
Script name: ./special_vars.sh
Process ID: 12345
Number of arguments: 4
=== All Arguments (using $*) ===
Arguments as single string: apple banana cherry pie 123
=== All Arguments (using $@) ===
Arguments as separate items:
- apple
- banana
- cherry pie
- 123
```
Basic Argument Passing Examples
Let's explore practical examples that demonstrate common argument passing scenarios:
Example 1: File Processing Script
```bash
#!/bin/bash
File: process_file.sh
if [ $# -eq 0 ]; then
echo "Usage: $0 "
exit 1
fi
filename=$1
if [ ! -f "$filename" ]; then
echo "Error: File '$filename' does not exist."
exit 1
fi
echo "Processing file: $filename"
echo "File size: $(wc -c < "$filename") bytes"
echo "Line count: $(wc -l < "$filename") lines"
echo "Word count: $(wc -w < "$filename") words"
```
Example 2: Mathematical Calculator
```bash
#!/bin/bash
File: calculator.sh
if [ $# -ne 3 ]; then
echo "Usage: $0 "
echo "Operators: +, -, *, /"
exit 1
fi
num1=$1
operator=$2
num2=$3
case $operator in
"+")
result=$((num1 + num2))
;;
"-")
result=$((num1 - num2))
;;
"*")
result=$((num1 * num2))
;;
"/")
if [ $num2 -eq 0 ]; then
echo "Error: Division by zero"
exit 1
fi
result=$((num1 / num2))
;;
*)
echo "Error: Invalid operator '$operator'"
exit 1
;;
esac
echo "$num1 $operator $num2 = $result"
```
Advanced Argument Handling Techniques
Using shift Command
The `shift` command is powerful for processing arguments sequentially:
```bash
#!/bin/bash
File: shift_example.sh
echo "Processing arguments with shift:"
while [ $# -gt 0 ]; do
echo "Current argument: $1 (Remaining: $#)"
shift
done
echo "All arguments processed."
```
Processing Arguments with Loops
```bash
#!/bin/bash
File: loop_args.sh
echo "Method 1: Using positional parameters"
for i in $(seq 1 $#); do
eval arg=\$$i
echo "Argument $i: $arg"
done
echo
echo "Method 2: Using \$@ (recommended)"
counter=1
for arg in "$@"; do
echo "Argument $counter: $arg"
((counter++))
done
```
Validating and Processing Arguments
Proper argument validation is crucial for robust scripts. Here are comprehensive validation techniques:
Input Validation Script
```bash
#!/bin/bash
File: validate_args.sh
Function to validate email format
validate_email() {
local email=$1
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
return 0
else
return 1
fi
}
Function to validate numeric input
validate_number() {
local num=$1
if [[ $num =~ ^[0-9]+$ ]]; then
return 0
else
return 1
fi
}
Main validation logic
if [ $# -ne 3 ]; then
echo "Usage: $0 "
exit 1
fi
name=$1
email=$2
age=$3
Validate name (non-empty, alphabetic characters and spaces only)
if [[ -z "$name" || ! $name =~ ^[a-zA-Z[:space:]]+$ ]]; then
echo "Error: Invalid name. Use only letters and spaces."
exit 1
fi
Validate email
if ! validate_email "$email"; then
echo "Error: Invalid email format."
exit 1
fi
Validate age
if ! validate_number "$age" || [ $age -lt 1 ] || [ $age -gt 150 ]; then
echo "Error: Age must be a number between 1 and 150."
exit 1
fi
echo "Validation successful!"
echo "Name: $name"
echo "Email: $email"
echo "Age: $age"
```
Named Arguments and Options
For more sophisticated scripts, implement named arguments using getopts:
Basic getopts Example
```bash
#!/bin/bash
File: getopts_example.sh
Default values
verbose=false
output_file=""
input_file=""
Function to display usage
usage() {
echo "Usage: $0 [-v] [-o output_file] [-i input_file]"
echo " -v: Enable verbose mode"
echo " -o: Specify output file"
echo " -i: Specify input file"
exit 1
}
Parse command line options
while getopts "vo:i:h" opt; do
case $opt in
v)
verbose=true
;;
o)
output_file="$OPTARG"
;;
i)
input_file="$OPTARG"
;;
h)
usage
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
;;
:)
echo "Option -$OPTARG requires an argument." >&2
usage
;;
esac
done
Shift past the options
shift $((OPTIND-1))
Display parsed options
echo "=== Parsed Options ==="
echo "Verbose mode: $verbose"
echo "Output file: ${output_file:-"Not specified"}"
echo "Input file: ${input_file:-"Not specified"}"
echo "Remaining arguments: $@"
```
Advanced Option Parsing
```bash
#!/bin/bash
File: advanced_options.sh
Initialize variables
declare -A config
config[verbose]=false
config[debug]=false
config[port]=8080
config[host]="localhost"
config[config_file]=""
Function to load configuration from file
load_config() {
local file=$1
if [ -f "$file" ]; then
source "$file"
echo "Configuration loaded from $file"
else
echo "Warning: Configuration file $file not found"
fi
}
Parse long and short options
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
config[verbose]=true
shift
;;
-d|--debug)
config[debug]=true
shift
;;
-p|--port)
config[port]="$2"
shift 2
;;
-h|--host)
config[host]="$2"
shift 2
;;
-c|--config)
config[config_file]="$2"
load_config "$2"
shift 2
;;
--help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -v, --verbose Enable verbose output"
echo " -d, --debug Enable debug mode"
echo " -p, --port Set port number (default: 8080)"
echo " -h, --host Set host address (default: localhost)"
echo " -c, --config Load configuration from file"
echo " --help Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
Display configuration
echo "=== Final Configuration ==="
for key in "${!config[@]}"; do
echo "$key: ${config[$key]}"
done
```
Working with Variable Number of Arguments
Processing Multiple Files
```bash
#!/bin/bash
File: process_multiple.sh
if [ $# -eq 0 ]; then
echo "Usage: $0 [file2] [file3] ..."
exit 1
fi
total_files=0
total_lines=0
total_words=0
echo "Processing $# files..."
echo
for file in "$@"; do
if [ ! -f "$file" ]; then
echo "Warning: '$file' is not a valid file, skipping..."
continue
fi
lines=$(wc -l < "$file")
words=$(wc -w < "$file")
echo "File: $file"
echo " Lines: $lines"
echo " Words: $words"
echo
((total_files++))
((total_lines += lines))
((total_words += words))
done
echo "=== Summary ==="
echo "Files processed: $total_files"
echo "Total lines: $total_lines"
echo "Total words: $total_words"
```
Array-Based Argument Processing
```bash
#!/bin/bash
File: array_args.sh
Store all arguments in an array
args=("$@")
echo "Total arguments: ${#args[@]}"
echo
Process arguments by index
for i in "${!args[@]}"; do
echo "Argument $((i+1)): ${args[i]}"
done
echo
Filter arguments (example: only numeric ones)
numeric_args=()
for arg in "${args[@]}"; do
if [[ $arg =~ ^[0-9]+$ ]]; then
numeric_args+=("$arg")
fi
done
echo "Numeric arguments found: ${#numeric_args[@]}"
for num in "${numeric_args[@]}"; do
echo " - $num"
done
```
Common Issues and Troubleshooting
Issue 1: Spaces in Arguments
Problem: Arguments containing spaces are split incorrectly.
Solution: Always quote variables and use proper quoting:
```bash
#!/bin/bash
Wrong way
echo First argument: $1
Correct way
echo "First argument: $1"
When passing arguments with spaces
./script.sh "hello world" "another argument"
```
Issue 2: Empty Arguments
Problem: Script fails when no arguments are provided.
Solution: Always check argument count:
```bash
#!/bin/bash
File: check_args.sh
if [ $# -eq 0 ]; then
echo "Error: No arguments provided"
echo "Usage: $0 [arg2] ..."
exit 1
fi
Safe to process arguments
echo "Processing $# arguments..."
```
Issue 3: Special Characters in Arguments
Problem: Arguments with special characters cause unexpected behavior.
Solution: Proper quoting and escaping:
```bash
#!/bin/bash
File: special_chars.sh
for arg in "$@"; do
# Safe processing of arguments with special characters
echo "Processing: '$arg'"
# Escape special characters for shell commands
escaped_arg=$(printf '%q' "$arg")
echo "Escaped: $escaped_arg"
done
```
Issue 4: Argument Order Dependencies
Problem: Script breaks when arguments are provided in wrong order.
Solution: Use named parameters or flexible parsing:
```bash
#!/bin/bash
File: flexible_args.sh
Initialize with defaults
name=""
age=""
email=""
Parse arguments flexibly
for arg in "$@"; do
if [[ $arg == "@" ]]; then
email=$arg
elif [[ $arg =~ ^[0-9]+$ ]]; then
age=$arg
else
name=$arg
fi
done
echo "Name: $name"
echo "Age: $age"
echo "Email: $email"
```
Best Practices and Professional Tips
1. Always Validate Input
```bash
#!/bin/bash
File: validation_best_practice.sh
validate_input() {
local input=$1
local type=$2
case $type in
"number")
if ! [[ $input =~ ^[0-9]+$ ]]; then
echo "Error: '$input' is not a valid number"
return 1
fi
;;
"email")
if ! [[ $input =~ ^[^@]+@[^@]+\.[^@]+$ ]]; then
echo "Error: '$input' is not a valid email"
return 1
fi
;;
"file")
if [ ! -f "$input" ]; then
echo "Error: '$input' is not a valid file"
return 1
fi
;;
esac
return 0
}
Usage example
if ! validate_input "$1" "number"; then
exit 1
fi
```
2. Provide Clear Usage Information
```bash
#!/bin/bash
File: good_usage.sh
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Description:
This script demonstrates best practices for argument handling.
Arguments:
required_arg A required argument (must be provided)
Options:
-v, --verbose Enable verbose output
-h, --help Show this help message
-o, --output Specify output file
Examples:
$0 myfile.txt
$0 -v --output result.txt myfile.txt
$0 --help
EOF
}
Check for help option first
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
usage
exit 0
fi
```
3. Use Meaningful Variable Names
```bash
#!/bin/bash
File: meaningful_names.sh
Bad: unclear variable names
f=$1
n=$2
o=$3
Good: descriptive variable names
input_file=$1
max_lines=$2
output_format=$3
echo "Processing file: $input_file"
echo "Maximum lines: $max_lines"
echo "Output format: $output_format"
```
4. Implement Robust Error Handling
```bash
#!/bin/bash
File: error_handling.sh
set -euo pipefail # Exit on error, undefined variables, pipe failures
Error handling function
handle_error() {
echo "Error on line $1"
exit 1
}
trap 'handle_error $LINENO' ERR
Safe argument processing
process_arguments() {
if [ $# -lt 2 ]; then
echo "Error: Insufficient arguments"
echo "Usage: $0 "
exit 1
fi
local source=$1
local destination=$2
# Validate source exists
if [ ! -f "$source" ]; then
echo "Error: Source file '$source' does not exist"
exit 1
fi
# Process files safely
cp "$source" "$destination"
echo "Successfully copied '$source' to '$destination'"
}
process_arguments "$@"
```
Real-World Examples and Use Cases
Example 1: Backup Script
```bash
#!/bin/bash
File: backup_script.sh
Configuration
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
Function to display usage
usage() {
cat << EOF
Backup Script Usage: $0 [OPTIONS]
Options:
-d, --destination DIR Backup destination directory (default: $BACKUP_DIR)
-c, --compress Create compressed archive
-v, --verbose Enable verbose output
-e, --exclude PATTERN Exclude files matching pattern
-h, --help Show this help
Examples:
$0 /home/user/documents
$0 -c -v /home/user/documents
$0 --destination /external/backup --compress /home/user
EOF
}
Initialize variables
compress=false
verbose=false
exclude_pattern=""
destination=$BACKUP_DIR
Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-d|--destination)
destination="$2"
shift 2
;;
-c|--compress)
compress=true
shift
;;
-v|--verbose)
verbose=true
shift
;;
-e|--exclude)
exclude_pattern="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
-*)
echo "Unknown option: $1"
usage
exit 1
;;
*)
source_dir="$1"
shift
;;
esac
done
Validate required arguments
if [ -z "${source_dir:-}" ]; then
echo "Error: Source directory is required"
usage
exit 1
fi
if [ ! -d "$source_dir" ]; then
echo "Error: Source directory '$source_dir' does not exist"
exit 1
fi
Create destination directory if it doesn't exist
mkdir -p "$destination"
Perform backup
backup_name="backup_$(basename "$source_dir")_$DATE"
backup_path="$destination/$backup_name"
if $verbose; then
echo "Starting backup..."
echo "Source: $source_dir"
echo "Destination: $backup_path"
fi
if $compress; then
if [ -n "$exclude_pattern" ]; then
tar czf "$backup_path.tar.gz" --exclude="$exclude_pattern" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
else
tar czf "$backup_path.tar.gz" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
fi
echo "Compressed backup created: $backup_path.tar.gz"
else
if [ -n "$exclude_pattern" ]; then
rsync -av --exclude="$exclude_pattern" "$source_dir/" "$backup_path/"
else
rsync -av "$source_dir/" "$backup_path/"
fi
echo "Backup created: $backup_path"
fi
if $verbose; then
echo "Backup completed successfully!"
fi
```
Example 2: System Information Gatherer
```bash
#!/bin/bash
File: sysinfo.sh
Default configuration
output_file=""
include_network=true
include_disk=true
include_processes=false
format="text"
usage() {
cat << EOF
System Information Gatherer
Usage: $0 [OPTIONS]
Options:
-o, --output FILE Save output to file
-f, --format FORMAT Output format (text|json|html)
--no-network Skip network information
--no-disk Skip disk information
--include-processes Include process information
-h, --help Show this help
Examples:
$0 # Display all info to stdout
$0 -o sysinfo.txt # Save to file
$0 --format json -o sysinfo.json # JSON format
$0 --no-network --include-processes # Custom info selection
EOF
}
Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-o|--output)
output_file="$2"
shift 2
;;
-f|--format)
format="$2"
if [[ ! "$format" =~ ^(text|json|html)$ ]]; then
echo "Error: Invalid format. Use text, json, or html"
exit 1
fi
shift 2
;;
--no-network)
include_network=false
shift
;;
--no-disk)
include_disk=false
shift
;;
--include-processes)
include_processes=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
Function to gather system information
gather_info() {
case $format in
"text")
echo "=== System Information ==="
echo "Hostname: $(hostname)"
echo "OS: $(uname -s)"
echo "Kernel: $(uname -r)"
echo "Uptime: $(uptime -p 2>/dev/null || uptime)"
echo
if $include_network; then
echo "=== Network Information ==="
ip addr show 2>/dev/null | grep -E "inet|link" | head -10
echo
fi
if $include_disk; then
echo "=== Disk Usage ==="
df -h
echo
fi
if $include_processes; then
echo "=== Top Processes ==="
ps aux --sort=-%cpu | head -10
fi
;;
"json")
echo "{"
echo " \"hostname\": \"$(hostname)\","
echo " \"os\": \"$(uname -s)\","
echo " \"kernel\": \"$(uname -r)\","
echo " \"uptime\": \"$(uptime -p 2>/dev/null || uptime)\""
echo "}"
;;
"html")
echo "System Information"
echo "
System Information
"
echo "
Hostname: $(hostname)
"
echo "
OS: $(uname -s)
"
echo "
Kernel: $(uname -r)
"
echo ""
;;
esac
}
Generate output
if [ -n "$output_file" ]; then
gather_info > "$output_file"
echo "System information saved to: $output_file"
else
gather_info
fi
```
Conclusion
Mastering argument passing in shell scripts is essential for creating flexible, reusable, and professional automation tools. Throughout this comprehensive guide, we've covered everything from basic positional parameters to advanced argument parsing techniques.
Key Takeaways
1. Use positional parameters (`$1`, `$2`, etc.) for simple argument handling
2. Leverage special variables (`$#`, `$@`, `$*`) for dynamic argument processing
3. Implement proper validation to ensure script reliability and user-friendly error messages
4. Use getopts or manual parsing for named arguments and options
5. Follow best practices including clear usage information, meaningful variable names, and robust error handling
Next Steps
To further improve your shell scripting skills:
1. Practice implementing the examples provided in this guide
2. Experiment with different argument parsing techniques in your own scripts
3. Study existing shell scripts to see how they handle arguments
4. Learn about advanced topics like subcommands and configuration file parsing
5. Consider using external libraries like `argparse` for complex argument handling needs
Professional Development
As you continue developing shell scripts:
- Always prioritize user experience with clear error messages and help text
- Implement comprehensive input validation to prevent security issues
- Use consistent naming conventions and coding standards
- Document your scripts thoroughly, including usage examples
- Test your scripts with various argument combinations and edge cases
Remember that well-designed argument handling makes the difference between a script that works for you and a script that others can confidently use in production environments. The techniques covered in this guide will help you create robust, professional-grade shell scripts that stand the test of time.
By implementing these argument passing techniques, you'll be able to create shell scripts that are not only functional but also maintainable, user-friendly, and suitable for both personal automation and professional deployment scenarios.