Introduction to Bash Scripting
Bash (Bourne Again Shell) scripting is a powerful way to automate tasks, combine commands, and create complex programs in Unix/Linux environments. Bash scripts are text files containing a series of commands that can be executed sequentially or with control flow logic.
Key Concepts
- Shebang: The first line
#!/bin/bashthat tells the system which interpreter to use - Variables: Store and manipulate data
- Control Flow: Conditionals, loops, and case statements
- Functions: Reusable code blocks
- Input/Output: Reading user input and displaying output
- Exit Status: Every command returns a status code (0 for success, non-zero for failure)
1. Bash Script Basics
Script Structure and Shebang
#!/bin/bash # This is a comment # Filename: myscript.sh echo "Hello, World!" # Make script executable: # chmod +x myscript.sh # Run: ./myscript.sh
Script Execution Methods
# Method 1: Make executable and run chmod +x script.sh ./script.sh # Method 2: Run with bash interpreter bash script.sh # Method 3: Source the script (runs in current shell) source script.sh . script.sh # Method 4: Run in subshell ( bash script.sh ) # Debug mode bash -x script.sh # Print each command before execution bash -v script.sh # Print input lines as they are read bash -n script.sh # Check syntax without executing
Basic Script Template
#!/bin/bash # Script Name: template.sh # Description: Template for bash scripts # Author: Your Name # Date: $(date +%Y-%m-%d) # Version: 1.0 # Exit on error, undefined variable, and pipe failure set -euo pipefail # Script variables SCRIPT_NAME=$(basename "$0") SCRIPT_DIR=$(dirname "$0") VERSION="1.0" # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging functions log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } # Usage function usage() { cat << EOF Usage: $SCRIPT_NAME [OPTIONS] Options: -h, --help Show this help message -v, --verbose Verbose output -f, --file Input file -o, --output Output file Examples: $SCRIPT_NAME -f input.txt -o output.txt EOF } # Parse command line arguments parse_args() { while [[ $# -gt 0 ]]; do case $1 in -h|--help) usage exit 0 ;; -v|--verbose) VERBOSE=true shift ;; -f|--file) INPUT_FILE="$2" shift 2 ;; -o|--output) OUTPUT_FILE="$2" shift 2 ;; *) log_error "Unknown option: $1" usage exit 1 ;; esac done } # Main function main() { log_info "Starting $SCRIPT_NAME version $VERSION" # Your script logic here if [[ -n "${INPUT_FILE:-}" ]]; then log_info "Processing file: $INPUT_FILE" fi log_info "Script completed successfully" } # Trap exit for cleanup cleanup() { log_info "Cleaning up..." # Remove temp files, etc. } trap cleanup EXIT # Run main function with all arguments parse_args "$@" main 2. Variables and Data Types
Variable Declaration and Usage
#!/bin/bash # Variable assignment (no spaces around =) name="John" age=25 pi=3.14159 # Using variables echo "Name: $name" echo "Age: ${age}" # Braces optional, but useful for complex expressions # Read-only variables readonly MAX_RETRIES=5 # Unset variables unset name # Command substitution current_date=$(date +%Y-%m-%d) files_count=$(ls -1 | wc -l) # Arithmetic result=$((5 + 3 * 2)) counter=$((counter + 1)) # String concatenation greeting="Hello, " "World!" # This works full_greeting="Hello, $name!" # Arrays fruits=("apple" "banana" "orange") fruits[3]="grape" echo "First fruit: ${fruits[0]}" echo "All fruits: ${fruits[@]}" echo "Number of fruits: ${#fruits[@]}" # Associative arrays (bash 4+) declare -A capitals capitals["USA"]="Washington" capitals["France"]="Paris" echo "Capital of USA: ${capitals["USA"]}" # Environment variables echo "Home directory: $HOME" echo "User: $USER" echo "Path: $PATH" # Special variables echo "Script name: $0" echo "First argument: $1" echo "All arguments: $@" echo "Number of arguments: $#" echo "Last command exit status: $?" echo "Process ID: $$" Variable Scope
#!/bin/bash # Global variable global_var="I'm global" function test_scope() { # Local variable local local_var="I'm local" echo "Inside function: $global_var" # Can access global echo "Inside function: $local_var" # Can access local # Modify global global_var="Modified in function" } test_scope echo "Outside function: $global_var" # Modified echo "Outside function: $local_var" # Empty (not accessible) # Export variables to child processes export exported_var="Visible to child processes" 3. Input and Output
Reading Input
#!/bin/bash # Read single line echo "Enter your name:" read name echo "Hello, $name!" # Read with prompt read -p "Enter your age: " age # Read password (silent) read -sp "Enter password: " password echo # Read with timeout read -t 5 -p "Quick! Enter something (5 seconds): " quick_input # Read multiple variables read -p "Enter first and last name: " first last # Read into array read -a colors -p "Enter colors (space-separated): " # Read from file while read line; do echo "Line: $line" done < file.txt # Read with custom IFS old_IFS=$IFS IFS="," read -p "Enter comma-separated values: " val1 val2 val3 IFS=$old_IFS # Heredoc input cat << EOF > output.txt Line 1 Line 2 Line 3 EOF # Here string grep "pattern" <<< "this is a test string"
Output and Formatting
#!/bin/bash # Basic echo echo "Simple message" # Echo without newline echo -n "This will not have a newline" # Enable escape sequences echo -e "Line1\nLine2\tTabbed" # Printf (more formatting control) printf "Name: %s, Age: %d\n" "John" 25 printf "Price: %.2f\n" 19.995 # Formatting table printf "%-10s %-8s %s\n" "Name" "Age" "City" printf "%-10s %-8d %s\n" "John" 25 "New York" printf "%-10s %-8d %s\n" "Alice" 30 "London" # Color output RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color echo -e "${RED}Error message${NC}" echo -e "${GREEN}Success message${NC}" # Redirect output echo "To file" > output.txt # Overwrite echo "Append to file" >> output.txt # Append # Redirect errors command 2> error.txt # Only errors command > output.txt 2>&1 # Both stdout and stderr command &> all_output.txt # Both (bash 4+) # Multiple redirections command > stdout.txt 2> stderr.txt # Here document cat <<- EOF > script.sh # This is a generated script echo "Hello World" EOF # Process substitution diff <(ls dir1) <(ls dir2) # Named pipes (FIFO) mkfifo mypipe command1 > mypipe & command2 < mypipe 4. Control Structures
Conditional Statements (if-elif-else)
#!/bin/bash # Basic if if [ "$name" = "John" ]; then echo "Hello John" fi # if-else if [ -f "$file" ]; then echo "File exists" else echo "File does not exist" fi # if-elif-else if [ "$score" -ge 90 ]; then echo "Grade A" elif [ "$score" -ge 80 ]; then echo "Grade B" elif [ "$score" -ge 70 ]; then echo "Grade C" else echo "Grade F" fi # Test conditions # File tests [ -f "$file" ] # File exists and is regular file [ -d "$dir" ] # Directory exists [ -e "$path" ] # Path exists [ -r "$file" ] # File is readable [ -w "$file" ] # File is writable [ -x "$file" ] # File is executable [ -s "$file" ] # File is not empty [ -L "$link" ] # File is symbolic link # String tests [ -z "$str" ] # String is empty [ -n "$str" ] # String is not empty [ "$str1" = "$str2" ] # Strings equal [ "$str1" != "$str2" ] # Strings not equal [[ "$str1" == *pattern* ]] # Pattern matching (bash) # Numeric tests [ "$a" -eq "$b" ] # Equal [ "$a" -ne "$b" ] # Not equal [ "$a" -lt "$b" ] # Less than [ "$a" -le "$b" ] # Less than or equal [ "$a" -gt "$b" ] # Greater than [ "$a" -ge "$b" ] # Greater than or equal # Logical operators [ "$a" -gt 0 ] && [ "$a" -lt 10 ] # AND [ "$a" -lt 0 ] || [ "$a" -gt 10 ] # OR [ ! -f "$file" ] # NOT # Double brackets (bash-specific, more features) if [[ "$str" == *substring* ]]; then echo "Contains substring" fi if [[ "$str" =~ ^[0-9]+$ ]]; then echo "All digits" fi # Ternary-like expression result=$([ "$a" -gt "$b" ] && echo "a" || echo "b")
Case Statements
#!/bin/bash # Basic case case "$option" in start) echo "Starting service" ;; stop) echo "Stopping service" ;; restart) echo "Restarting service" ;; status) echo "Checking status" ;; *) echo "Unknown option: $option" exit 1 ;; esac # Case with multiple patterns case "$file" in *.txt|*.text) echo "Text file" ;; *.jpg|*.jpeg|*.png|*.gif) echo "Image file" ;; *.sh) echo "Shell script" ;; *) echo "Unknown file type" ;; esac # Case with ranges and character classes case "$char" in [0-9]) echo "Digit" ;; [a-z]) echo "Lowercase letter" ;; [A-Z]) echo "Uppercase letter" ;; ?) echo "Single character" ;; *) echo "Other" ;; esac # Case with fall-through (using ;& or ;;& in bash) case "$value" in pattern1) echo "Pattern 1" ;& # Continue to next pattern pattern2) echo "Pattern 2" ;; esac
Loops
#!/bin/bash # For loop with list for fruit in apple banana orange; do echo "I like $fruit" done # For loop with range for i in {1..5}; do echo "Number: $i" done # For loop with step for i in {1..10..2}; do echo "Odd: $i" done # C-style for loop for ((i=0; i<10; i++)); do echo "i = $i" done # For loop with array fruits=("apple" "banana" "orange") for fruit in "${fruits[@]}"; do echo "Fruit: $fruit" done # For loop with command output for file in $(ls *.txt); do echo "Text file: $file" done # While loop count=1 while [ $count -le 5 ]; do echo "Count: $count" ((count++)) done # While loop reading file while IFS= read -r line; do echo "Line: $line" done < file.txt # Until loop (opposite of while) until [ $count -gt 10 ]; do echo "Count: $count" ((count++)) done # Infinite loops while true; do echo "Press Ctrl+C to exit" sleep 1 done # Loop control for i in {1..10}; do if [ $i -eq 5 ]; then continue # Skip rest of iteration fi if [ $i -eq 8 ]; then break # Exit loop fi echo "i = $i" done # Nested loops for i in {1..3}; do for j in {1..3}; do echo "($i, $j)" done done # Loop with select menu select option in "Option 1" "Option 2" "Option 3" "Quit"; do case $option in "Option 1") echo "You chose option 1" ;; "Option 2") echo "You chose option 2" ;; "Option 3") echo "You chose option 3" ;; "Quit") break ;; *) echo "Invalid option" ;; esac done 5. Functions
Function Definition and Usage
#!/bin/bash # Simple function greet() { echo "Hello, $1!" } # Call function greet "John" # Function with multiple parameters add_numbers() { local sum=$(( $1 + $2 )) echo $sum } result=$(add_numbers 5 3) echo "Result: $result" # Function with return value (exit status) is_file_exists() { if [ -f "$1" ]; then return 0 # Success else return 1 # Failure fi } if is_file_exists "file.txt"; then echo "File exists" fi # Function returning string get_user_info() { local name="John" local age=25 echo "$name:$age" # Return as string } # Parse returned string IFS=':' read -r name age <<< "$(get_user_info)" # Function with local variables process_data() { local input="$1" local temp_var="temporary" echo "Processing: $input" # temp_var is not accessible outside } # Function with default arguments log_message() { local level="${2:-INFO}" # Default to INFO echo "[$level] $1" } log_message "Starting process" log_message "Error occurred" "ERROR" # Recursive function factorial() { if [ $1 -le 1 ]; then echo 1 else local prev=$(factorial $(( $1 - 1 ))) echo $(( $1 * prev )) fi } echo "Factorial of 5: $(factorial 5)" # Function library # Save as lib.sh #!/bin/bash function1() { ... } function2() { ... } # Source in main script source ./lib.sh function1 6. Arrays
Array Operations
#!/bin/bash # Indexed arrays fruits=("apple" "banana" "orange") fruits[3]="grape" # Access elements echo "${fruits[0]}" # First element echo "${fruits[@]}" # All elements echo "${fruits[*]}" # All elements (as single string) echo "${#fruits[@]}" # Array length echo "${!fruits[@]}" # Array indices # Slicing echo "${fruits[@]:1:2}" # Elements 1 and 2 # Adding elements fruits+=("kiwi" "mango") # Removing elements unset fruits[1] # Remove element at index 1 # Associative arrays (bash 4+) declare -A capitals capitals=( ["USA"]="Washington" ["France"]="Paris" ["Japan"]="Tokyo" ) # Access associative arrays echo "${capitals["USA"]}" echo "${!capitals[@]}" # Keys echo "${capitals[@]}" # Values echo "${#capitals[@]}" # Number of elements # Loop through array for fruit in "${fruits[@]}"; do echo "Fruit: $fruit" done # Loop with index for i in "${!fruits[@]}"; do echo "Index $i: ${fruits[$i]}" done # Loop through associative array for country in "${!capitals[@]}"; do echo "Capital of $country is ${capitals[$country]}" done # Array from command output files=($(ls *.txt)) # Array operations # Join elements joined=$(IFS=,; echo "${fruits[*]}") # Check if element exists if [[ " ${fruits[@]} " =~ " apple " ]]; then echo "Apple found" fi # Sort array sorted=($(for fruit in "${fruits[@]}"; do echo $fruit; done | sort)) # Unique values unique=($(printf "%s\n" "${fruits[@]}" | sort -u)) 7. String Manipulation
String Operations
#!/bin/bash # String length str="Hello World" echo "${#str}" # 11 # Substring extraction echo "${str:0:5}" # Hello echo "${str:6}" # World echo "${str: -5}" # World (negative index) # Substring replacement echo "${str/World/Everyone}" # Replace first match echo "${str//l/L}" # Replace all matches echo "${str/#Hello/Hi}" # Replace at beginning echo "${str/%World/Planet}" # Replace at end # Pattern removal echo "${str#He*}" # Remove shortest matching prefix echo "${str##H* }" # Remove longest matching prefix echo "${str%l*}" # Remove shortest matching suffix echo "${str%%l*}" # Remove longest matching suffix # Case conversion (bash 4+) echo "${str^^}" # Uppercase echo "${str,,}" # Lowercase echo "${str~}" # Reverse case of first character echo "${str~~}" # Reverse case of all characters # Default values unset var echo "${var:-default}" # Use default if unset/null echo "${var:=default}" # Assign default if unset echo "${var:?error}" # Error if unset/null echo "${var:+alternative}" # Use alternative if set # String splitting IFS=' ' read -ra words <<< "$str" # String joining joined=$(IFS=,; echo "${words[*]}") # Check if string contains substring if [[ "$str" == *"World"* ]]; then echo "Contains World" fi # Regular expression matching if [[ "$str" =~ ^Hello.*World$ ]]; then echo "Pattern matches" fi # Trim whitespace trimmed=$(echo "$str" | xargs) # Pad string printf "%10s\n" "$str" # Right align printf "%-10s\n" "$str" # Left align 8. File Operations
File Handling
#!/bin/bash # Check file properties if [ -f "$file" ]; then echo "Regular file"; fi if [ -d "$dir" ]; then echo "Directory"; fi if [ -L "$link" ]; then echo "Symbolic link"; fi if [ -r "$file" ]; then echo "Readable"; fi if [ -w "$file" ]; then echo "Writable"; fi if [ -x "$file" ]; then echo "Executable"; fi if [ -s "$file" ]; then echo "Not empty"; fi if [ -e "$path" ]; then echo "Exists"; fi # File comparison if [ "$file1" -nt "$file2" ]; then echo "file1 newer"; fi if [ "$file1" -ot "$file2" ]; then echo "file1 older"; fi if [ "$file1" -ef "$file2" ]; then echo "same file"; fi # File operations cp source dest mv old new rm file mkdir -p path/to/dir rmdir emptydir rm -rf dir # Reading files while IFS= read -r line; do echo "$line" done < file.txt # Writing to files echo "content" > file.txt echo "more" >> file.txt # File descriptors exec 3< input.txt # Open for reading exec 4> output.txt # Open for writing exec 5>> append.txt # Open for appending read <&3 line echo "$line" >&4 exec 3<&- # Close file descriptor exec 4>&- # Temporary files temp_file=$(mktemp) temp_dir=$(mktemp -d) trap "rm -f $temp_file; rm -rf $temp_dir" EXIT # File locking exec 3>file.lock flock -n 3 || exit 1 # Critical section flock -u 3 # Watch file for changes inotifywait -m file.txt | while read path action file; do echo "File changed: $action" done
9. Error Handling and Debugging
Error Handling Techniques
#!/bin/bash # Exit on error set -e # Exit on any error set -u # Exit on undefined variable set -o pipefail # Exit on pipe failure # Combined set -euo pipefail # Trap errors trap 'echo "Error on line $LINENO"' ERR # Trap exit trap 'cleanup' EXIT # Trap signals trap 'echo "Interrupted"; exit 1' INT TERM # Custom error function error_exit() { echo "Error: $1" >&2 exit 1 } # Usage [ -f "$file" ] || error_exit "File not found: $file" # Check command success if command; then echo "Success" else echo "Failed with exit code $?" fi # Retry logic retry() { local n=1 local max=5 local delay=2 while true; do if "$@"; then break else if [[ $n -lt $max ]]; then ((n++)) echo "Command failed. Attempt $n/$max:" sleep $delay else echo "The command has failed after $n attempts." return 1 fi fi done } # Debug mode debug() { if [ "${DEBUG:-false}" = "true" ]; then echo "DEBUG: $*" >&2 fi } DEBUG=true debug "Variable value: $var" # Logging levels log() { local level="$1" shift local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $*" } log "INFO" "Processing started" log "ERROR" "Something went wrong" # Assert function assert() { if ! "$@"; then echo "Assertion failed: $*" >&2 exit 1 fi } assert [ -f "$config_file" ] 10. Advanced Bash Features
Process Management
#!/bin/bash # Background processes long_running_task & pid=$! # Wait for specific PID wait $pid # Wait for all background jobs wait # Job control jobs # List jobs fg %1 # Bring job 1 to foreground bg %1 # Put job 1 in background kill %1 # Kill job 1 # Process substitution diff <(ls dir1) <(ls dir2) # Coprocesses coproc myproc { command; } # Send signal kill -TERM $pid kill -0 $pid # Check if process exists # Process groups set -m # Enable job control kill -- -$PGID # Kill process group # Daemonize daemonize() { (cd /; exec "$@" </dev/null >/dev/null 2>&1 &) } # PID file management pidfile="/var/run/myscript.pid" if [ -f "$pidfile" ]; then pid=$(cat "$pidfile") if kill -0 "$pid" 2>/dev/null; then echo "Already running with PID $pid" exit 1 fi fi echo $$ > "$pidfile" Regular Expressions
#!/bin/bash # =~ operator for regex matching if [[ "$string" =~ ^[0-9]+$ ]]; then echo "All digits" fi # Capture groups if [[ "$string" =~ ([0-9]{3})-([0-9]{4}) ]]; then echo "Area: ${BASH_REMATCH[1]}" echo "Number: ${BASH_REMATCH[2]}" fi # Common patterns [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] [[ "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] # Using grep with regex grep -E "^[0-9]+$" file.txt grep -P "\d+" file.txt # Perl-compatible regex # Using sed with regex sed -E 's/[0-9]{3}-[0-9]{4}/(xxx)/g' file.txt # Using awk with regex awk '/^[0-9]+$/ {print}' file.txt Here Documents and Here Strings
#!/bin/bash # Here document cat << EOF > file.txt Line 1 Line 2 Line 3 EOF # Here document with variable expansion name="John" cat << EOF Hello $name, This is a multi-line message. EOF # Here document without expansion cat << 'EOF' Variable $name will not be expanded EOF # Here document with leading tabs stripped cat <<- EOF This line starts with tabs which will be stripped. EOF # Here string grep "pattern" <<< "this is a test string" # Multiple here documents command1 << EOF1 data1 EOF1 command2 << EOF2 data2 EOF2 # Here document as input while read line; do echo "Read: $line" done << EOF line1 line2 line3 EOF
11. Practical Script Examples
System Information Script
#!/bin/bash # system_info.sh - Display system information set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' print_header() { echo -e "${BLUE}================================${NC}" echo -e "${GREEN}$1${NC}" echo -e "${BLUE}================================${NC}" } get_system_info() { print_header "System Information" echo "Hostname: $(hostname)" echo "OS: $(uname -s -r)" echo "Uptime: $(uptime | awk '{print $3,$4}' | sed 's/,//')" echo "Load average: $(uptime | awk -F'load average:' '{print $2}')" print_header "CPU Information" echo "Model: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs)" echo "Cores: $(nproc)" echo "Architecture: $(uname -m)" print_header "Memory Information" free -h | awk 'NR==1{print $1 "\t" $2 "\t" $3 "\t" $4} NR==2{print $1 "\t" $2 "\t" $3 "\t" $4}' print_header "Disk Usage" df -h | grep -E "^/dev/" | awk '{print $1 "\t" $2 "\t" $3 "\t" $4 "\t" $5}' print_header "Network Information" ip -4 addr show | grep inet | awk '{print $2 "\t" $NF}' | sed 's/\/[0-9]*//' print_header "Top 5 Processes by CPU" ps aux --sort=-%cpu | head -6 | awk '{print $1 "\t" $2 "\t" $3 "%\t" $11}' } # Optional: save to file if [ "$1" = "-f" ]; then get_system_info > "system_info_$(date +%Y%m%d_%H%M%S).txt" else get_system_info fi Backup Script
#!/bin/bash # backup.sh - Automated backup script set -euo pipefail # Configuration BACKUP_DIR="/backup" SOURCE_DIRS=("/home/user/documents" "/home/user/pictures") EXCLUDE=("*.tmp" "*.log" ".cache") RETENTION_DAYS=7 LOG_FILE="/var/log/backup.log" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' log() { local level="$1" shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE" } error_exit() { log "ERROR" "$1" exit 1 } check_prerequisites() { # Check if backup directory exists if [ ! -d "$BACKUP_DIR" ]; then mkdir -p "$BACKUP_DIR" || error_exit "Cannot create backup directory" fi # Check if source directories exist for dir in "${SOURCE_DIRS[@]}"; do if [ ! -d "$dir" ]; then error_exit "Source directory not found: $dir" fi done # Check available space local available=$(df "$BACKUP_DIR" | awk 'NR==2 {print $4}') if [ "$available" -lt 1048576 ]; then # Less than 1GB free error_exit "Insufficient disk space" fi } create_backup() { local timestamp=$(date '+%Y%m%d_%H%M%S') local backup_file="${BACKUP_DIR}/backup_${timestamp}.tar.gz" log "INFO" "Starting backup to $backup_file" # Build exclude options local exclude_opts=() for pattern in "${EXCLUDE[@]}"; do exclude_opts+=("--exclude=$pattern") done # Create backup if tar -czf "$backup_file" "${exclude_opts[@]}" "${SOURCE_DIRS[@]}" 2>> "$LOG_FILE"; then local size=$(du -h "$backup_file" | cut -f1) log "INFO" "Backup completed successfully. Size: $size" # Create checksum md5sum "$backup_file" > "${backup_file}.md5" log "INFO" "Checksum created" else error_exit "Backup failed" fi } rotate_backups() { log "INFO" "Rotating backups older than $RETENTION_DAYS days" find "$BACKUP_DIR" -name "backup_*.tar.gz" -type f -mtime +"$RETENTION_DAYS" -print0 | while IFS= read -r -d '' file; do rm -f "$file" "${file}.md5" log "INFO" "Removed old backup: $(basename "$file")" done } verify_backup() { local latest=$(ls -t "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | head -1) if [ -n "$latest" ]; then log "INFO" "Verifying latest backup: $(basename "$latest")" # Verify checksum if md5sum -c "${latest}.md5" >> "$LOG_FILE" 2>&1; then log "INFO" "Checksum verification passed" else log "ERROR" "Checksum verification failed" return 1 fi # Test archive integrity if tar -tzf "$latest" > /dev/null 2>&1; then log "INFO" "Archive integrity check passed" else log "ERROR" "Archive integrity check failed" return 1 fi else log "WARNING" "No backup found to verify" fi } # Main execution main() { log "INFO" "=== Backup Script Started ===" check_prerequisites create_backup verify_backup rotate_backups log "INFO" "=== Backup Script Completed ===" } # Trap errors trap 'error_exit "Script interrupted"' INT TERM trap 'log "INFO" "Script completed"' EXIT # Run main main "$@" File Organizer Script
#!/bin/bash # organize.sh - Organize files by type set -euo pipefail # Configuration TARGET_DIR="${1:-.}" ORGANIZE_BY="${2:-extension}" # extension, date, size DRY_RUN=false # Color output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' usage() { cat << EOF Usage: $0 [directory] [option] Options: -e, --extension Organize by file extension (default) -d, --date Organize by modification date -s, --size Organize by file size -n, --dry-run Show what would be done without actually moving -h, --help Show this help message EOF } log() { echo -e "${GREEN}[INFO]${NC} $1" } warn() { echo -e "${YELLOW}[WARN]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } move_file() { local file="$1" local dest="$2" if [ "$DRY_RUN" = true ]; then log "Would move: $(basename "$file") -> $dest/" else mkdir -p "$dest" mv -v "$file" "$dest/" | sed 's/^/ /' fi } organize_by_extension() { log "Organizing by extension in $TARGET_DIR" find "$TARGET_DIR" -maxdepth 1 -type f | while read -r file; do # Skip if file is in a subdirectory if [ "$(dirname "$file")" != "$TARGET_DIR" ]; then continue fi # Get extension ext="${file##*.}" # Handle files without extension if [ "$ext" = "$file" ] || [ -z "$ext" ]; then ext="no_extension" fi # Convert to lowercase ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]') # Create destination directory dest="$TARGET_DIR/$ext" move_file "$file" "$dest" done } organize_by_date() { log "Organizing by date in $TARGET_DIR" find "$TARGET_DIR" -maxdepth 1 -type f | while read -r file; do if [ "$(dirname "$file")" != "$TARGET_DIR" ]; then continue fi # Get modification date year=$(date -r "$file" +%Y) month=$(date -r "$file" +%m) dest="$TARGET_DIR/$year/$month" move_file "$file" "$dest" done } organize_by_size() { log "Organizing by size in $TARGET_DIR" find "$TARGET_DIR" -maxdepth 1 -type f | while read -r file; do if [ "$(dirname "$file")" != "$TARGET_DIR" ]; then continue fi # Get file size in bytes size=$(stat -c%s "$file") # Categorize by size if [ "$size" -lt 1024 ]; then category="tiny" # < 1KB elif [ "$size" -lt 1048576 ]; then category="small" # < 1MB elif [ "$size" -lt 10485760 ]; then category="medium" # < 10MB elif [ "$size" -lt 104857600 ]; then category="large" # < 100MB else category="huge" # > 100MB fi dest="$TARGET_DIR/$category" move_file "$file" "$dest" done } # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -e|--extension) ORGANIZE_BY="extension" shift ;; -d|--date) ORGANIZE_BY="date" shift ;; -s|--size) ORGANIZE_BY="size" shift ;; -n|--dry-run) DRY_RUN=true shift ;; -h|--help) usage exit 0 ;; -*) error "Unknown option: $1" usage exit 1 ;; *) TARGET_DIR="$1" shift ;; esac done # Validate target directory if [ ! -d "$TARGET_DIR" ]; then error "Directory not found: $TARGET_DIR" exit 1 fi # Main execution log "Starting file organization in: $TARGET_DIR" case "$ORGANIZE_BY" in extension) organize_by_extension ;; date) organize_by_date ;; size) organize_by_size ;; esac log "Organization complete!" # Show summary if [ "$DRY_RUN" = false ]; then log "Final directory structure:" tree -L 2 "$TARGET_DIR" 2>/dev/null || ls -R "$TARGET_DIR" | head -20 fi 12. Best Practices and Tips
Script Header Template
#!/usr/bin/env bash #=============================================================================== # # FILE: script.sh # USAGE: ./script.sh [options] # # DESCRIPTION: Brief description of what the script does # # OPTIONS: see usage() function # REQUIREMENTS: List required commands/packages # BUGS: --- # NOTES: --- # AUTHOR: Your Name, [email protected] # VERSION: 1.0 # CREATED: $(date +%Y-%m-%d) # REVISION: --- #=============================================================================== set -euo pipefail # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" # Source configuration if exists [ -f "$SCRIPT_DIR/config.sh" ] && source "$SCRIPT_DIR/config.sh" # Default values VERBOSE=false DEBUG=false #=============================================================================== # FUNCTIONS #=============================================================================== usage() { cat << EOF Usage: $SCRIPT_NAME [OPTIONS] Options: -h, --help Show this help message -v, --verbose Verbose output -d, --debug Debug mode EOF } log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } error() { log "ERROR: $*" >&2 exit 1 } debug() { [ "$DEBUG" = true ] && log "DEBUG: $*" } #=============================================================================== # MAIN SCRIPT #=============================================================================== main() { log "Starting $SCRIPT_NAME" # Your code here log "Finished $SCRIPT_NAME" } # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -h|--help) usage exit 0 ;; -v|--verbose) VERBOSE=true shift ;; -d|--debug) DEBUG=true shift ;; --) shift break ;; -*) error "Unknown option: $1" ;; *) break ;; esac done # Trap exit trap 'error "Script interrupted"' INT TERM trap 'log "Script completed"' EXIT # Run main main "$@"
Coding Standards
#!/bin/bash # 1. Use meaningful variable names # Good user_name="John" file_count=$(ls -1 | wc -l) # Bad n="John" c=$(ls -1 | wc -l) # 2. Use local variables in functions process_file() { local file="$1" local temp_var="temporary" # ... } # 3. Quote variables # Good rm -f "$file" # Bad rm -f $file # Breaks if filename has spaces # 4. Use [[ ]] for tests (bash) # Good [[ "$name" == "John" ]] && [[ -f "$file" ]] # 5. Use (( )) for arithmetic # Good ((result = a + b)) # Bad result=$((a + b)) # Also acceptable, but (( )) is cleaner for conditions # 6. Use functions for reusable code cleanup() { rm -f "$temp_file" rm -rf "$temp_dir" } # 7. Error handling command || { echo "Command failed"; exit 1; } # 8. Use readonly for constants readonly MAX_RETRIES=5 # 9. Use arrays for lists files=("$file1" "$file2" "$file3") # 10. Document complex code # This function calculates fibonacci numbers recursively # Args: n - position in fibonacci sequence # Returns: fibonacci number fibonacci() { # implementation } Performance Tips
#!/bin/bash # 1. Avoid unnecessary subshells # Slow cat file.txt | while read line; do echo "$line" done # Fast while read line; do echo "$line" done < file.txt # 2. Use bash builtins instead of external commands # Slow count=$(echo "$string" | wc -c) # Fast count=${#string} # 3. Avoid loops when possible # Slow for file in *; do mv "$file" "${file}.bak" done # Fast rename 's/$/.bak/' * # 4. Use parameter expansion instead of sed/awk # Slow new_string=$(echo "$string" | sed 's/foo/bar/g') # Fast new_string="${string//foo/bar}" # 5. Group commands # Slow command1 command2 command3 > output.txt # Fast { command1 command2 command3 } > output.txt # 6. Use process substitution to avoid pipes # Slow command1 | command2 | command3 # Fast (if possible) command3 < <(command2 < <(command1)) # 7. Limit external command calls # Slow for i in {1..100}; do echo "$i" done | grep "5" # Fast for i in {1..100}; do [[ "$i" == *5* ]] && echo "$i" done # 8. Use exec for redirections exec 3> output.txt echo "to file" >&3 exec 3>&- Conclusion
Bash scripting is a powerful skill for system administration, automation, and development:
Key Takeaways
- Start with shebang: Always start with
#!/bin/bash - Use functions: Organize code into reusable functions
- Error handling: Always check for errors and handle them gracefully
- Quote variables: Prevent word splitting and pathname expansion
- Use arrays: For lists of items
- Validate input: Check arguments and user input
- Document code: Comments and usage information
- Test thoroughly: Test edge cases and error conditions
Common Patterns
| Pattern | Use Case |
|---|---|
set -euo pipefail | Strict mode for robustness |
[[ -f "$file" ]] | File existence check |
while read line | Reading files line by line |
for i in {1..10} | Numeric loops |
case $var in | Multiple condition checks |
trap cleanup EXIT | Automatic cleanup |
"${var:-default}" | Default values |
"${var##*/}" | Basename extraction |
Best Practices Checklist
- [ ] Use
#!/usr/bin/env bashfor portability - [ ] Set strict mode:
set -euo pipefail - [ ] Quote all variable expansions
- [ ] Use local variables in functions
- [ ] Check return codes of commands
- [ ] Provide usage information
- [ ] Validate input parameters
- [ ] Use meaningful variable names
- [ ] Add comments for complex logic
- [ ] Handle signals and cleanup
- [ ] Test with
shellcheckfor errors - [ ] Use version control for scripts
Bash scripting is an essential skill that grows with practice. Start with simple scripts and gradually incorporate more advanced features as you become comfortable with the basics.
Complete Bash Exercises with Solutions
Practice Bash scripting with solved exercises to improve your command-line and scripting skills.
Link: https://macronepal.com/complete-bash-exercises-with-solutions/
Complete Guide to Bash Crontab Command
Learn how to schedule and automate tasks efficiently using the Bash crontab command.
Link: https://macronepal.com/complete-guide-to-bash-crontab-command-schedule-tasks/
Complete Guide to Bash Arrays
Understand how to create, manage, and use arrays in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-arrays/
Bash Quiz: Test Your Knowledge
Test your Bash scripting knowledge with quizzes and practical questions.
Link: https://macronepal.com/bash-quiz-test-your-knowledge/
Complete Guide to Bash Functions
Learn how to write reusable Bash functions for cleaner and efficient scripts.
Link: https://macronepal.com/complete-guide-to-bash-functions/
Complete Guide to Bash Loops
Master loops in Bash for repetitive tasks and automation.
Link: https://macronepal.com/complete-guide-to-bash-loops/
Complete Guide to Bash If-Else Statements
Learn decision-making in Bash using conditional statements.
Link: https://macronepal.com/complete-guide-to-bash-ifelse-statements/
Complete Guide to Bash Operators
Explore arithmetic, logical, and comparison operators in Bash.
Link: https://macronepal.com/complete-guide-to-bash-operators/
Complete Guide to Bash Data Types
Understand variables and different data types used in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-data-types/
Complete Guide to Bash Variables
Learn how to declare, assign, and use variables in Bash scripts.
Link: https://macronepal.com/complete-guide-to-bash-variables/
Complete Guide to Bash Scripting
A complete introduction to writing Bash scripts for automation.
Link: https://macronepal.com/complete-guide-to-bash-scripting/
Complete Guide to Basic Bash Syntax
Learn the fundamental syntax needed to write Bash commands and scripts.
Link: https://macronepal.com/complete-guide-to-basic-bash-syntax/
Complete Guide to Bash Chgrp Command
Learn how to change group ownership of files and directories.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership-2/
Complete Guide to Bash Chgrp Command (Alternate)
Another detailed explanation of Bash group ownership management.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership/
Complete Guide to Bash File Permissions and Ownership
Understand Linux file permissions and ownership management.
Link: https://macronepal.com/complete-guide-to-bash-file-permissions-and-ownership/
Complete Guide to Bash Chmod Command
Learn how to modify file permissions using chmod.
Link: https://macronepal.com/complete-guide-to-bash-chmod-command-change-file-permissions/
Complete Guide to Bash Chown Command
Change file ownership using the Bash chown command.
Link: https://macronepal.com/complete-guide-to-bash-chown-command-change-file-ownership/
Complete Guide to Bash SCP Command
Securely transfer files between systems using SCP.
Link: https://macronepal.com/complete-guide-to-bash-scp-command-secure-copy/
Complete Guide to Bash SSH Command
Learn secure remote access and server management using SSH.
Link: https://macronepal.com/complete-guide-to-bash-ssh-command/
Bash Wget Command Complete Guide
Download files from the internet using the wget command.
Link: https://macronepal.com/bash-wget-command-complete-guide-to-non-interactive-network-downloader/