Files
2025-11-30 09:05:19 +08:00

9.5 KiB

Bash Scripting Reference

Detailed patterns and examples for Bash automation scripts.

Error Handling

Essential Settings

Put these at the top of every Bash script:

#!/usr/bin/env bash
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT

cleanup() {
    trap - SIGINT SIGTERM ERR EXIT
    # Cleanup code here (remove temp files, etc.)
}

Flag Breakdown

-E (errtrap): Error traps work in functions

trap 'echo "Error"' ERR
func() { false; }  # Trap fires (wouldn't without -E)

-e (errexit): Stop on first error

command_fails  # Script exits here
never_runs     # Never executes

-u (nounset): Catch undefined variables

echo "$TYPO"  # Error: TYPO: unbound variable (not silent)

-o pipefail: Detect failures in pipes

false | true  # Fails (not just last command status)

trap: Run cleanup on exit/error/signal

String Escaping for LaTeX and Special Characters

Problem: Bash interprets escape sequences in double-quoted strings, which corrupts LaTeX commands and special text.

Dangerous sequences: \b (backspace), \n (newline), \t (tab), \r (return)

Example Failure

# ❌ Wrong: Creates backspace character
echo "\\begin{document}" >> file.tex  # Becomes: <backspace>egin{document}
echo "\\bibliographystyle{ACM}" >> file.tex  # Becomes: <backspace>ibliographystyle{ACM}

Safe Approaches

1. Single quotes (Best for simple cases):

echo '\begin{document}' >> file.tex  # ✅ No interpretation
echo '\bibliographystyle{ACM-Reference-Format}' >> file.tex  # ✅ Safe

2. Double backslashes (When variables needed):

echo "\\\\begin{document}" >> file.tex  # ✅ 4 backslashes → \b
cmd="begin"
echo "\\\\${cmd}{document}" >> file.tex  # ✅ Works with variables

3. Printf (More predictable):

printf '%s\n' '\begin{document}' >> file.tex  # ✅ Literal strings
printf '%s\n' '\bibliographystyle{ACM-Reference-Format}' >> file.tex

4. Heredoc (Best for multi-line LaTeX):

cat >> file.tex << 'EOF'  # ✅ Note quoted delimiter
\begin{document}
\section{Title}
\bibliographystyle{ACM-Reference-Format}
\end{document}
EOF

Quick Reference

Character Echo double-quotes Echo single-quotes Heredoc
\b Backspace Literal Literal
\n Newline Literal Literal
\t Tab Literal Literal
Variables Work Don't expand With "EOF"

Rule of thumb: For LaTeX, use single quotes or heredocs to avoid escape sequence interpretation.

Variable Quoting

Always Quote Variables

# ✅ Always quote variables
file="my file.txt"
cat "$file"          # Correct

# ❌ Unquoted breaks on spaces
cat $file            # WRONG: tries to cat "my" and "file.txt"

Array Expansion

files=("file 1.txt" "file 2.txt")

# ✅ Quote array expansion
for file in "${files[@]}"; do
    echo "$file"
done

# ❌ Unquoted splits on spaces
for file in ${files[@]}; do
    echo "$file"  # WRONG: treats spaces as separators
done

Script Directory Detection

# Get directory where script is located
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)

# Use for relative paths
source "${script_dir}/config.sh"
data_file="${script_dir}/../data/input.txt"

Functions

Function Template

# Document functions with comments
# Args:
#   $1 - input file
#   $2 - output file
# Returns:
#   0 on success, 1 on error
process_file() {
    local input="$1"
    local output="$2"

    if [[ ! -f "$input" ]]; then
        echo "Error: Input file not found: $input" >&2
        return 1
    fi

    # Process file
    grep pattern "$input" > "$output"
}

# Call function
if process_file "input.txt" "output.txt"; then
    echo "Success"
else
    echo "Failed" >&2
    exit 1
fi

Local Variables

Always use local for function variables:

process_data() {
    local data="$1"  # ✅ Local to function
    local result

    result=$(transform "$data")
    echo "$result"
}

Error Messages

Write Errors to Stderr

# ✅ Write errors to stderr
echo "Error: File not found" >&2

# ✅ Exit with non-zero code
exit 1

# ❌ Don't write errors to stdout
echo "Error: File not found"

Structured Error Handling

error() {
    echo "Error: $*" >&2
    exit 1
}

warn() {
    echo "Warning: $*" >&2
}

# Usage
[[ -f "$config" ]] || error "Config file not found: $config"
[[ -w "$output" ]] || warn "Output file not writable: $output"

Checking Commands Exist

if ! command -v jq &> /dev/null; then
    echo "Error: jq is required but not installed" >&2
    exit 1
fi

# Check multiple commands
for cmd in curl jq sed; do
    if ! command -v "$cmd" &> /dev/null; then
        echo "Error: $cmd is required but not installed" >&2
        exit 1
    fi
done

Parallel Processing

# Run commands in parallel, wait for all
for file in *.txt; do
    process_file "$file" &
done
wait

echo "All files processed"

Parallel with Error Handling

pids=()
for file in *.txt; do
    process_file "$file" &
    pids+=($!)
done

# Wait and check exit codes
failed=0
for pid in "${pids[@]}"; do
    if ! wait "$pid"; then
        ((failed++))
    fi
done

if [[ $failed -gt 0 ]]; then
    echo "Error: $failed jobs failed" >&2
    exit 1
fi

Configuration Files

Loading Config

# Load config file if exists
config_file="${script_dir}/config.sh"
if [[ -f "$config_file" ]]; then
    source "$config_file"
else
    # Default values
    LOG_DIR="/var/log"
    BACKUP_DIR="/backup"
fi

Safe Config Sourcing

# Validate config before sourcing
validate_config() {
    local config="$1"

    # Check syntax
    if ! bash -n "$config" 2>/dev/null; then
        echo "Error: Invalid syntax in $config" >&2
        return 1
    fi

    return 0
}

if validate_config "$config_file"; then
    source "$config_file"
else
    exit 1
fi

Argument Parsing

Simple Pattern

# Parse flags
VERBOSE=false
FORCE=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -f|--force)
            FORCE=true
            shift
            ;;
        -o|--output)
            OUTPUT="$2"
            shift 2
            ;;
        *)
            echo "Unknown option: $1" >&2
            exit 1
            ;;
    esac
done

Usage Function

usage() {
    cat << EOF
Usage: $0 [OPTIONS] INPUT OUTPUT

Process files with various options.

OPTIONS:
    -v, --verbose    Verbose output
    -f, --force      Force operation
    -o, --output     Output file
    -h, --help       Show this help

EXAMPLES:
    $0 input.txt output.txt
    $0 -v --force input.txt output.txt
EOF
}

# Show usage on error or -h
[[ "$1" == "-h" || "$1" == "--help" ]] && usage && exit 0
[[ $# -lt 2 ]] && usage && exit 1

Temporary Files

Safe Temp File Creation

# Create temp file
tmpfile=$(mktemp)
trap "rm -f '$tmpfile'" EXIT

# Use temp file
curl -s "$url" > "$tmpfile"
process "$tmpfile"

# Cleanup happens automatically via trap

Temp Directory

# Create temp directory
tmpdir=$(mktemp -d)
trap "rm -rf '$tmpdir'" EXIT

# Use temp directory
download_files "$tmpdir"
process_directory "$tmpdir"

Common Patterns

File Existence Checks

# Check file exists
[[ -f "$file" ]] || error "File not found: $file"

# Check directory exists
[[ -d "$dir" ]] || error "Directory not found: $dir"

# Check file readable
[[ -r "$file" ]] || error "File not readable: $file"

# Check file writable
[[ -w "$file" ]] || error "File not writable: $file"

String Comparisons

# Check empty string
[[ -z "$var" ]] && error "Variable is empty"

# Check non-empty string
[[ -n "$var" ]] || error "Variable not set"

# String equality
[[ "$a" == "$b" ]] && echo "Equal"

# Pattern matching
[[ "$file" == *.txt ]] && echo "Text file"

Numeric Comparisons

# Greater than
[[ $count -gt 10 ]] && echo "More than 10"

# Less than or equal
[[ $count -le 5 ]] && echo "5 or fewer"

# Equal
[[ $count -eq 0 ]] && echo "Zero"

Common Pitfalls

Unquoted Variables

file=$1
cat $file  # Breaks with spaces

Always Quote

file="$1"
cat "$file"

Escape Sequences in LaTeX

# Corrupts \begin, \bibitem, etc.
echo "\\begin{document}" >> file.tex  # Creates <backspace>egin

Use Single Quotes or Heredocs

echo '\begin{document}' >> file.tex
# Or:
cat >> file.tex << 'EOF'
\begin{document}
EOF

No Error Handling

#!/bin/bash
command_that_might_fail
continue_anyway

Fail Fast

#!/usr/bin/env bash
set -Eeuo pipefail
command_that_might_fail  # Script exits on failure

Unvalidated User Input

rm -rf /$user_input  # DANGER

Validate Input

# Validate directory name
if [[ ! "$user_input" =~ ^[a-zA-Z0-9_-]+$ ]]; then
    error "Invalid directory name"
fi

Validation Tools

# Check syntax
bash -n script.sh

# Static analysis with shellcheck
brew install shellcheck  # macOS
apt install shellcheck   # Ubuntu
shellcheck script.sh

# Run with debug mode
bash -x script.sh

References