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
- Bash error handling: https://bertvv.github.io/cheat-sheets/Bash.html
- ShellCheck: https://www.shellcheck.net/
- Bash best practices: https://mywiki.wooledge.org/BashGuide