$ cat /dev/knowledge | grep bash

Bash Grimoire

A curated collection of practical shell scripting patterns, tricks, and battle-tested examples

01 Fundamentals

Strict mode, argument handling, and the building blocks.

template.sh
#!/usr/bin/env bash
# Strict mode — catch errors early
set -euo pipefail
IFS=$'\n\t'

# Script directory (works with symlinks too)
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)

# Defaults
VERBOSE=0
OUTPUT="/tmp/output.log"

usage() {
    cat <<EOF
Usage: $(basename "$0") [OPTIONS] <input_file>

Options:
    -o, --output FILE    Output file (default: $OUTPUT)
    -v, --verbose        Enable verbose output
    -h, --help           Show this help
EOF
    exit 1
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        -o|--output)  OUTPUT="$2"; shift 2 ;;
        -v|--verbose) VERBOSE=1; shift ;;
        -h|--help)    usage ;;
        -*)            echo "Unknown option: $1" >&2; usage ;;
        *)             break ;;
    esac
done

[[ $# -lt 1 ]] && { echo "Error: input file required" >&2; usage; }
INPUT="$1"

log() { (( VERBOSE )) && printf '[%s] %s\n' "$(date +%T)" "$*" >&2; }

log "Processing $INPUT → $OUTPUT"
Tip: set -euo pipefail makes scripts fail fast. -e exits on error, -u catches unset variables, -o pipefail catches failures in piped commands.

02 String Operations

Parameter expansion is insanely powerful — skip sed for simple transforms.

strings.sh
file="/var/log/nginx/access.log.gz"

# Extraction
echo "${file##*/}"        # access.log.gz   (basename)
echo "${file%/*}"         # /var/log/nginx   (dirname)
echo "${file%%.*}"        # /var/log/nginx/access
echo "${file%.gz}"        # /var/log/nginx/access.log

# Substring: ${var:offset:length}
hash="a1b2c3d4e5f6"
echo "${hash:0:8}"        # a1b2c3d4

# Search & replace
path="/usr/local/bin"
echo "${path//\// > }"   #  > usr > local > bin

# Case conversion (bash 4+)
name="hello world"
echo "${name^^}"          # HELLO WORLD
echo "${name^}"           # Hello world

# Default values
echo "${EDITOR:-vim}"     # use vim if $EDITOR is unset
echo "${1:?'arg required'}" # exit with error if $1 empty

# Length
msg="hello"
echo "${#msg}"            # 5

03 Arrays & Associative Arrays

Arrays make complex data handling possible without reaching for Python.

arrays.sh
# Indexed arrays
servers=("web01" "web02" "db01" "cache01")
echo "First: ${servers[0]}"
echo "Count: ${#servers[@]}"
echo "All:   ${servers[*]}"

# Append
servers+=("monitor01")

# Slice: ${array[@]:offset:length}
echo "${servers[@]:1:2}"   # web02 db01

# Iterate with index
for i in "${!servers[@]}"; do
    printf '  [%d] %s\n' "$i" "${servers[$i]}"
done

# Associative arrays (bash 4+)
declare -A ports=(
    [nginx]=80
    [ssh]=22
    [postgres]=5432
    [redis]=6379
)

for svc in "${!ports[@]}"; do
    printf '%-12s → %s\n' "$svc" "${ports[$svc]}"
done

# Build array from command output
mapfile -t pids < <(pgrep -f nginx)
echo "nginx PIDs: ${pids[*]}"

04 Process Management

Traps, background jobs, and graceful signal handling.

process.sh
# Cleanup trap — runs on EXIT, INT, TERM
TMPDIR=$(mktemp -d)
cleanup() {
    rm -rf "$TMPDIR"
    echo "Cleaned up $TMPDIR"
}
trap cleanup EXIT

# Parallel execution with wait
for host in web0{1..4}; do
    ssh "$host" 'uptime' &
done
wait   # blocks until all background jobs finish

# Timeout a command
timeout 5 curl -sS "https://example.com" || echo "timed out"

# Lock file — prevent concurrent runs
LOCKFILE="/var/run/myscript.lock"
acquire_lock() {
    exec 200>"$LOCKFILE"
    flock -n 200 || { echo "Already running"; exit 1; }
}
acquire_lock

# Process substitution — diff two commands
diff <(ssh web01 'cat /etc/hosts') <(ssh web02 'cat /etc/hosts')
Warning: Avoid kill -9 as a first resort. Send SIGTERM first, wait, then escalate: kill $PID; sleep 2; kill -0 $PID 2>/dev/null && kill -9 $PID

05 File Processing

Safe file iteration, atomic writes, and log parsing.

files.sh
# Safe line-by-line reading
while IFS= read -r line; do
    echo "Processing: $line"
done < "input.txt"

# Read CSV fields
while IFS=',' read -r name ip role; do
    printf '%-15s %-15s %s\n' "$name" "$ip" "$role"
done < "servers.csv"

# Atomic write — never leave a half-written file
tmp=$(mktemp)
generate_config > "$tmp"
mv "$tmp" "/etc/app/config.yml"

# Find + exec (safer than piping to xargs)
find /var/log -name '*.log' -mtime +30 -exec gzip {} \;

# Watch a log file and react
tail -F /var/log/auth.log | while read -r line; do
    if [[ "$line" == *"Failed password"* ]]; then
        ip=$(grep -oP 'from \K[\d.]+' <<< "$line")
        echo "[$(date +%T)] Failed login from $ip"
    fi
done

06 Networking

HTTP requests, port scanning, and DNS queries — all from bash.

network.sh
# Health check with retry logic
check_endpoint() {
    local url="$1" retries=5 delay=3

    for (( i=1; i<=retries; i++ )); do
        if curl -sfSo /dev/null -w '%{http_code}' "$url" | grep -q '^2'; then
            echo "✓ $url is up"
            return 0
        fi
        echo "Attempt $i/$retries failed, retrying in ${delay}s..."
        sleep "$delay"
    done
    echo "✗ $url is DOWN"
    return 1
}

# Quick port check (no netcat needed)
port_open() {
    timeout 2 bash -c "echo > /dev/tcp/$1/$2" 2>/dev/null
}
port_open "google.com" 443 && echo "open" || echo "closed"

# Resolve DNS and extract IPs
dig +short "example.com" A | while read -r ip; do
    echo "  $ip → $(dig +short -x "$ip" | head -1)"
done

# Download with progress + resume
curl -LO -C - --retry 3 "https://releases.example.com/app.tar.gz"

# SSL cert expiry check
cert_expiry() {
    echo | openssl s_client -servername "$1" -connect "$1:443" 2>/dev/null \
        | openssl x509 -noout -enddate | cut -d= -f2
}
echo "Cert expires: $(cert_expiry vk.rtscom.com)"

07 Design Patterns

Reusable patterns for production scripts.

patterns.sh
# ── Colored logging ──
declare -A _c=([red]='\e[31m' [green]='\e[32m' [yellow]='\e[33m' [reset]='\e[0m')
log_info()  { printf "${_c[green]}[INFO]${_c[reset]}  %s\n" "$*"; }
log_warn()  { printf "${_c[yellow]}[WARN]${_c[reset]}  %s\n" "$*" >&2; }
log_error() { printf "${_c[red]}[ERROR]${_c[reset]} %s\n" "$*" >&2; }

# ── Retry wrapper ──
retry() {
    local n=0 max="$1" delay="$2"
    shift 2
    until "$@"; do
        (( ++n >= max )) && { log_error "Failed after $n attempts: $*"; return 1; }
        log_warn "Attempt $n failed, retrying in ${delay}s..."
        sleep "$delay"
    done
}
retry 3 5 curl -sf "https://api.example.com/health"

# ── Config file parser ──
parse_config() {
    local file="$1"
    while IFS='=' read -r key value; do
        [[ "$key" =~ ^[[:space:]]*# ]] && continue
        [[ -z "$key" ]] && continue
        printf -v "CFG_${key^^}" '%s' "${value## }"
    done < "$file"
}
# After: parse_config app.conf
# Access: echo "$CFG_DATABASE_HOST"

# ── Spinner for long operations ──
spinner() {
    local pid="$1" chars='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
    while kill -0 "$pid" 2>/dev/null; do
        for (( i=0; i<${#chars}; i++ )); do
            printf '\r  %s Working...' "${chars:$i:1}"
            sleep 0.1
        done
    done
    printf '\r  ✓ Done!       \n'
}
# Usage: long_task & spinner $!

08 Useful One-Liners

Copy-paste ready commands for daily sysadmin work.

oneliners.sh
# Top 10 largest files in current directory tree
find . -type f -exec du -h {} + | sort -rh | head -10

# Kill all processes matching a pattern
pkill -f 'pattern'     # SIGTERM
pkill -9 -f 'pattern'  # SIGKILL (last resort)

# Monitor disk usage changes in real time
watch -n5 'df -h | grep -E "/$|/var|/home"'

# List open ports with process names
ss -tlnp

# Show all unique IPs hitting nginx today
awk '{print $1}' /var/log/nginx/access.log | sort -u | wc -l

# Generate a random 32-char password
openssl rand -base64 24

# Quick HTTP server from current directory
python3 -m http.server 8080

# Replace string in all files recursively
grep -rl 'old_string' . | xargs sed -i 's/old_string/new_string/g'

# Compare two directories
diff <(cd dir1 && find . | sort) <(cd dir2 && find . | sort)

# Backup with timestamp
tar czf "backup-$(date +%F_%H%M).tar.gz" /etc/nginx/

09 Quick Reference

Test operators, redirections, and special variables.

ExpressionDescription
[[ -f $f ]]True if file exists and is regular
[[ -d $d ]]True if directory exists
[[ -z $s ]]True if string is empty
[[ -n $s ]]True if string is not empty
[[ $a == $b ]]String equality
[[ $a =~ regex ]]Regex match
[[ $n -gt 0 ]]Numeric comparison (also: -lt, -eq, -ne, -ge, -le)
VariableMeaning
$?Exit status of last command
$$PID of current shell
$!PID of last background process
$#Number of positional parameters
$@All parameters (individually quoted)
$0Script name
${PIPESTATUS[@]}Exit codes of all commands in last pipeline
RedirectMeaning
cmd > fileStdout to file (overwrite)
cmd >> fileStdout to file (append)
cmd 2>&1Stderr to stdout
cmd &> fileBoth stdout+stderr to file
cmd1 | cmd2Pipe stdout to next command
cmd1 |& cmd2Pipe stdout+stderr
cmd < <(cmd2)Process substitution