Linux: Bash Scripting Advanced

Basic scripts can handle simple tasks, but production-grade scripts need more robust code — functions to split logic, error handling to prevent "silent failures," and parameter parsing to make scripts more usable.

📋 Prerequisites: You should already know

1. What You'll Learn


2. A Story of Xiaoming's Script Running Wild on the Server

(1) The Pain Point: Script Continues Running After Errors

Xiaoming wrote a deployment script:

BASH
#!/bin/bash
cd /var/www/myapp
git pull origin main
npm install            # ❌ This failed
npm run build          # But the script kept going
pm2 restart app        # Restarted with old code and broken build

(2) Adding One Line Makes It Safe

Bob said: "Add set -e at the top — it stops on any error."

TEXT
#!/bin/bash
set -e                 # Stop on error
cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart app

(3) The Benefit: Safety First

With set -e, if npm install fails, the script exits immediately instead of restarting the service with broken code.


3. Knowledge Points

(1) Functions

TEXT
# Define a function
greet() {
    echo "Hello, $1!"
    echo "Today is $(date)"
}

# Alternative syntax
function greet {
    echo "Hello, $1!"
}

# Call it
greet "Alice"

# Local variables
myfunc() {
    local x=10        # Local variable, doesn't affect outer scope
    echo "Inside: $x"
}
x=5
myfunc                # Output: Inside: 10
echo "Outside: $x"    # Output: Outside: 5

(2) Arrays

TEXT
# Indexed array
fruits=("apple" "banana" "orange")
fruits[3]="grape"     # Add element

echo ${fruits[0]}     # apple
echo ${fruits[@]}     # All elements
echo ${#fruits[@]}    # Array length

# Iterate over array
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

# Associative array (like a dictionary)
declare -A config
config[host]="localhost"
config[port]="8080"
config[user]="admin"

echo ${config[host]}  # localhost

(3) Error Handling

TEXT
# set -e: Exit on error
set -e

# set -u: Error on undefined variable
set -u

# set -o pipefail: Pipeline fails if any command fails
set -o pipefail

# Common combination
set -euo pipefail

> 💡 Tip: `set -euo pipefail` is the "safety trio" for production scripts — `-e` stops on error, `-u` catches undefined variables, `-o pipefail` makes pipeline failures propagate. All non-trivial scripts should include this line.

# trap: Execute cleanup on signal or exit
trap 'echo "Script interrupted"; exit 1' INT TERM
trap 'rm -f /tmp/tempfile; echo "Cleanup done"' EXIT

> 💡 Tip: `trap` can automatically execute cleanup when a script exits — whether it ends normally or is interrupted by Ctrl+C, `trap EXIT` is triggered. Common uses: deleting temporary files, releasing file locks, restoring configs. Write `trap 'rm -f $TMPFILE' EXIT` at the top and you won't need manual cleanup at every exit point.

# Custom error handler
error_handler() {
    echo "❌ Error on line $1"
    echo "Command: $2"
    exit 1
}
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR

(4) getopts — Parameter Parsing

TEXT
# getopts syntax
while getopts "f:o:hv" opt; do
    case $opt in
        f) INPUT_FILE="$OPTARG" ;;   # -f filename
        o) OUTPUT_DIR="$OPTARG" ;;   # -o output
        h) show_help; exit 0 ;;      # -h help
        v) VERBOSE=true ;;           # -v verbose
        *) echo "Unknown option"; exit 1 ;;
    esac
done

> ℹ️ Note: `getopts` is bash's built-in parameter parser — in the option string `"f:o:hv"`, a colon `:` means the option requires an argument (e.g., `-f filename`), no colon means it's a flag option (e.g., `-v`). It doesn't support long options (like `--help`); for those, use the external `getopt` command or manual parsing.

(5) Debugging Techniques

TEXT
# Debug mode: show each command as it executes
set -x

# Turn off debugging
set +x

# Enable debugging for a specific section
echo "Normal execution"
set -x
echo "This section is traced"
for i in 1 2 3; do echo "$i"; done
set +x
echo "Back to normal"

# Or debug at script startup
# bash -x script.sh

(6) ▶ Example: Function Encapsulation

TEXT
#!/bin/bash
# functions-demo.sh - Function examples

log() {
    local level="${1:-INFO}"
    local message="$2"
    echo "[$(date '+%H:%M:%S')] [$level] $message"
}

die() {
    log "ERROR" "$1"
    exit 1
}

check_dependency() {
    if ! command -v "$1" &> /dev/null; then
        die "Required command '$1' not found"
    fi
}

# Using the functions
log "INFO" "Starting dependency check..."
check_dependency "git"
check_dependency "node"
log "INFO" "Dependency check passed"

(7) ▶ Example: Array Operations

TEXT
#!/bin/bash
# array-demo.sh - Array examples

# Server list
servers=("web01" "web02" "db01" "cache01")

echo "Server count: ${#servers[@]}"

echo "--- Iterating servers ---"
for server in "${servers[@]}"; do
    echo "Checking: $server"
done

echo "--- Associative array example ---"
declare -A disk_usage
disk_usage[/]="45%"
disk_usage[/home]="62%"
disk_usage[/var]="78%"

for mount in "${!disk_usage[@]}"; do
    echo "$mount: ${disk_usage[$mount]}"
done

Output:

TEXT
[10:23:01] [INFO] Starting dependency check...
[10:23:01] [INFO] Dependency check passed
Server count: 4
--- Iterating servers ---
Checking: web01
Checking: web02
Checking: db01
Checking: cache01
--- Associative array example ---
/: 45%
/home: 62%
/var: 78%

(8) ▶ Example: set -e / trap Error Handling

BASH
#!/bin/bash
# safe-script.sh - Robust script

set -euo pipefail

cleanup() {
    echo "Cleaning temporary files..."
    rm -f /tmp/temp_*.txt
    echo "Done"
}

error_handler() {
    echo "❌ Error occurred on line $1"
    cleanup
    exit 1
}

trap error_handler ERR
trap cleanup EXIT

echo "Starting execution..."
mkdir -p /tmp/test
touch /tmp/test/file.txt
cat /tmp/test/file.txt
# If cat fails on a non-existent file, the script stops and triggers ERR trap

Output:

TEXT
Starting execution...
Cleaning temporary files...
Done

(9) ▶ Example: getopts Parameter Parsing

BASH
#!/bin/bash
# backup-tool.sh - Backup tool with parameter parsing

usage() {
    echo "Usage: $0 -s <source_dir> -d <dest_dir> [-c] [-v]"
    echo "  -s  Source directory to back up"
    echo "  -d  Backup destination directory"
    echo "  -c  Compress backup"
    echo "  -v  Verbose output"
    echo "  -h  Show help"
    exit 0
}

# Default values
COMPRESS=false
VERBOSE=false

while getopts "s:d:cvh" opt; do
    case $opt in
        s) SOURCE="$OPTARG" ;;
        d) DEST="$OPTARG" ;;
        c) COMPRESS=true ;;
        v) VERBOSE=true ;;
        h) usage ;;
        *) usage ;;
    esac
done

# Validate required parameters
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
    echo "Error: Must specify -s and -d"
    usage
fi

if [ ! -d "$SOURCE" ]; then
    echo "Error: Source directory '$SOURCE' does not exist"
    exit 1
fi

# Execute backup
mkdir -p "$DEST"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_${TIMESTAMP}"

if [ "$COMPRESS" = true ]; then
    tar -czf "${DEST}/${BACKUP_NAME}.tar.gz" "$SOURCE"
    echo "✅ Compressed backup complete: ${DEST}/${BACKUP_NAME}.tar.gz"
else
    cp -r "$SOURCE" "${DEST}/${BACKUP_NAME}"
    echo "✅ Backup complete: ${DEST}/${BACKUP_NAME}"
fi

if [ "$VERBOSE" = true ]; then
    echo "Source: $SOURCE"
    echo "Destination: $DEST"
    echo "Size: $(du -sh "$SOURCE" | cut -f1)"
fi

Output:

TEXT
Usage: ./backup-tool.sh -s <source_dir> -d <dest_dir> [-c] [-v]
  -s  Source directory to back up
  -d  Backup destination directory
  -c  Compress backup
  -v  Verbose output
  -h  Show help
✅ Compressed backup complete: /backup/backup_20260707_102345.tar.gz
Source: /var/www/myapp
Destination: /backup
Size: 12M

(10) ▶ Example: Debugging Techniques

TEXT
#!/bin/bash
# debug-demo.sh - Debugging example

# Only enable tracing where debugging is needed
echo "Script started PID: $$"

for i in 1 2 3; do
    set -x              # Start tracing
    echo "Loop iteration $i"
    result=$((i * 10))
    echo "Result: $result"
    set +x              # Stop tracing
done

echo "Script execution complete"

(11) ▶ Comprehensive Example: Production-Grade Deployment Script

BASH
#!/bin/bash
# deploy.sh - Production-grade deployment script

set -euo pipefail

APP_NAME="webapp"
APP_DIR="/var/www/$APP_NAME"
BACKUP_DIR="/backup/$APP_NAME"
LOG_FILE="/var/log/deploy.log"

# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

log() {
    local level="$1"
    local msg="$2"
    echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${msg}" | tee -a "$LOG_FILE"
}

info()  { log "INFO" "$1"; }
success() { log "INFO" "${GREEN}$1${NC}"; }
error() { log "ERROR" "${RED}$1${NC}"; }

cleanup() {
    info "Cleaning deployment temporary files..."
}

error_handler() {
    error "Deployment failed at line $1"
    error "Command: $BASH_COMMAND"
    cleanup
    exit 1
}

usage() {
    echo "Usage: $0 [options]"
    echo "  -b   Specify Git branch (default: main)"
    echo "  -e   Specify environment (dev/staging/prod)"
    echo "  -n   Skip backup"
    echo "  -h   Show help"
    exit 0
}

trap error_handler ERR
trap cleanup EXIT

# --- Parameter parsing ---
BRANCH="main"
ENV="prod"
SKIP_BACKUP=false

while getopts "b:e:nh" opt; do
    case $opt in
        b) BRANCH="$OPTARG" ;;
        e) ENV="$OPTARG" ;;
        n) SKIP_BACKUP=true ;;
        h) usage ;;
        *) usage ;;
    esac
done

# --- Environment check ---
info "Starting deployment of $APP_NAME (branch: $BRANCH, environment: $ENV)"

if [ ! -d "$APP_DIR" ]; then
    error "Application directory $APP_DIR does not exist"
    exit 1
fi

# --- Backup ---
if [ "$SKIP_BACKUP" = false ]; then
    info "Backing up current version..."
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    mkdir -p "$BACKUP_DIR"
    tar -czf "${BACKUP_DIR}/${TIMESTAMP}.tar.gz" \
        --exclude=node_modules \
        --exclude=.git \
        "$APP_DIR"
    success "Backup complete: ${BACKUP_DIR}/${TIMESTAMP}.tar.gz"
fi

# --- Deploy ---
cd "$APP_DIR"

info "Pulling latest code..."
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"

info "Installing dependencies..."
npm ci --production

info "Building application..."
if [ "$ENV" = "production" ]; then
    npm run build:prod
else
    npm run build
fi

info "Restarting service..."
pm2 restart "$APP_NAME" || pm2 start npm --name "$APP_NAME" -- start

# --- Health check ---
info "Running health check..."
sleep 3
if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health | grep -q "200"; then
    success "✅ Deployment successful! Health check passed"
else
    error "❌ Health check failed, please investigate manually"
    exit 1
fi

❓ FAQ

Q: What's the difference between functions and aliases? A: Functions are more powerful than aliases: they support parameters, local variables, return values, and reuse. Aliases are just simple command substitutions. Use functions for complex logic, aliases for simple shortcuts.

Q: When does set -e not work? A: set -e doesn't trigger in these cases: 1) In if/while/until condition expressions. 2) After && or ||. 3) In pipelines (add set -o pipefail). 4) When a command is negated with !.

Q: Which signals can trap catch? A: Common ones: EXIT (script exits), ERR (command fails), INT (Ctrl+C), TERM (default kill signal). Others include HUP, QUIT, USR1, USR2.

Q: What's the difference between getopts and getopt? A: getopts is a bash built-in, simpler, doesn't support long options (like --help). getopt is an external command (from util-linux package) that supports mixing long and short options. Prefer getopts for compatibility.

Q: How to create temporary files in a script? A: Use mktemp: tempfile=$(mktemp) creates a temp file, tempdir=$(mktemp -d) creates a temp directory. Temp files are usually created in /tmp; remember to delete them when done.


📖 Summary


📝 Exercises

  1. Basic (⭐): Refactor the Lesson 20 backup script using functions (log/die/check_dependency etc.), and add set -euo pipefail and trap error handling
  2. Intermediate (⭐⭐): Add -s (source directory) and -d (destination directory) parameters using getopts, create an associative array to store config, and iterate to output all config items
  3. Challenge (⭐⭐⭐): Use mktemp to create a temporary directory that auto-cleans on completion, and write a production-grade deployment script — with parameter parsing, colored logging, error handling, and health check
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏