How to pass arguments to shell scripts

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.