All topics
Frontend · Learning hub

Bash notes for developers

Master Bash with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Bash

Shell Scripting

Shell Scripting Script Basics #!/usr/bin/env bash # Shebang — use env to find bash in PATH # Best practice: set errexit, pipefail, nounset set -euo pipefail # -

Shell Scripting

Script Basics

#!/usr/bin/env bash
# Shebang — use env to find bash in PATH
# Best practice: set errexit, pipefail, nounset
set -euo pipefail
# -e: exit on error
# -u: error on undefined variable
# -o pipefail: pipe fails if any command fails

# Variables
NAME="Alice"
AGE=28
GREETING="Hello, $NAME"     # double quotes allow expansion
LITERAL='Hello, $NAME'      # single quotes: literal string

# Read-only constants
readonly MAX_RETRIES=3

# Command substitution
TODAY=$(date +%Y-%m-%d)
FILES=$(ls *.sh 2>/dev/null | wc -l)

# Arithmetic
((count++))
((total = a + b))
result=$((10 * 5 - 3))
echo $((2 ** 8))    # 256

Conditionals & Loops

# If / elif / else
if [[ -f "$FILE" ]]; then
  echo "File exists"
elif [[ -d "$FILE" ]]; then
  echo "Directory exists"
else
  echo "Not found"
fi

# Test operators
# Files: -f (file), -d (dir), -e (exists), -r (readable), -x (executable)
# Strings: -z (empty), -n (non-empty), = (equal), != (not equal)
# Numbers: -eq, -ne, -lt, -le, -gt, -ge
# Logic: && (and), || (or), ! (not)

if [[ "$USER" = "root" ]] && [[ -x "/usr/local/bin/app" ]]; then
  echo "root user with app"
fi

if [[ -z "$DB_URL" ]]; then
  echo "Error: DB_URL not set" >&2
  exit 1
fi

# Case
case "$ENV" in
  production|prod)
    PORT=443; DEBUG=false;;
  staging|stage)
    PORT=8443; DEBUG=true;;
  *)
    PORT=3000; DEBUG=true;;
esac

# For loops
for file in *.log; do
  echo "Processing: $file"
  gzip "$file"
done

for i in {1..5}; do echo "$i"; done
for ((i=0; i<10; i++)); do echo "$i"; done
for item in "${array[@]}"; do echo "$item"; done

# While loop
while IFS= read -r line; do
  echo "Line: $line"
done < input.txt

# Until loop
until [[ -f "$LOCK_FILE" ]]; do
  sleep 1
done

Functions & Arrays

# Functions
log() {
  local level="$1"
  local message="$2"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" >&2
}

retry() {
  local max_attempts="$1"
  local delay="$2"
  shift 2
  local command=("$@")
  local attempt=1
  while (( attempt <= max_attempts )); do
    "${command[@]}" && return 0
    log "WARN" "Attempt $attempt/$max_attempts failed. Retrying in ${delay}s..."
    sleep "$delay"
    ((attempt++))
  done
  log "ERROR" "Command failed after $max_attempts attempts"
  return 1
}

# Return values via echo (not return — that's for exit codes 0-255)
get_user() {
  local id="$1"
  echo "User_$id"  # "return" data via echo
}
user=$(get_user 42)

# Arrays
files=("app.ts" "index.ts" "types.ts")
files+=("utils.ts")                    # append
echo "${files[0]}"                     # first element
echo "${files[@]}"                     # all elements
echo "${#files[@]}"                    # length
echo "${files[@]:1:2}"                 # slice [1,2]
unset files[1]                         # remove element

# Associative arrays (bash 4+)
declare -A config
config["host"]="localhost"
config["port"]="5432"
echo "${config[host]}"
for key in "${!config[@]}"; do echo "$key=${config[$key]}"; done

Practical Patterns

#!/usr/bin/env bash
set -euo pipefail

# Script directory (works even when sourced)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Cleanup on exit / error
cleanup() {
  echo "Cleaning up..."
  rm -f /tmp/tempfile.$$
}
trap cleanup EXIT
trap 'echo "Error on line $LINENO"; cleanup; exit 1' ERR

# Check required commands
require_cmd() {
  command -v "$1" &>/dev/null || { echo "Error: $1 not found" >&2; exit 1; }
}
require_cmd docker
require_cmd kubectl

# Parse arguments
usage() {
  cat <<EOF
Usage: $(basename "$0") [options]
  -e, --env       Environment (dev|staging|prod)
  -v, --verbose   Verbose output
  -h, --help      Show this help
EOF
  exit 1
}

ENV="dev"
VERBOSE=false
while [[ $# -gt 0 ]]; do
  case "$1" in
    -e|--env) ENV="$2"; shift 2;;
    -v|--verbose) VERBOSE=true; shift;;
    -h|--help) usage;;
    *) echo "Unknown option: $1" >&2; usage;;
  esac
done

# Validate input
[[ "$ENV" =~ ^(dev|staging|prod)$ ]] || { echo "Invalid env: $ENV" >&2; exit 1; }

# Load .env file
if [[ -f ".env.$ENV" ]]; then
  set -o allexport
  source ".env.$ENV"
  set +o allexport
fi

Keep your Bash knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever