Bash Scripting Cheat Sheet
Essential, battle-tested syntax reference for writing robust, fail-safe Bash and POSIX shell scripts. Includes strict mode boilerplate, parameter expansions, loops, regex conditionals, and automatic cleanup traps.
Safe Bash Script Header (Unofficial Strict Mode)
Enforces strict execution: -e exits immediately on error, -u treats unset variables as errors, -o pipefail preserves pipeline exit codes, and IFS prevents dangerous whitespace splitting.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
echo "Running safe script under $(bash --version | head -n1)"Portable Shebang
Uses the system's PATH environment to locate the bash binary, ensuring compatibility across macOS, Debian, Alpine, and RHEL.
#!/usr/bin/env bashScript Directory Resolution (CD to Script Location)
Resolves the absolute path of the directory containing the currently executing script, regardless of where the user invoked it from.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"cd "$SCRIPT_DIR"
source "$SCRIPT_DIR/config.env"Debug Mode (Print Commands as They Execute)
Prints each command with expanded variables to stderr preceded by a '+' before execution. Invaluable for step-by-step troubleshooting.
set -x # enable trace
# commands...
set +x # disable traceDefault Value If Unset or Empty
Returns VAR if defined and non-empty; otherwise evaluates and returns 'default_value'. Does not mutate VAR.
${VAR:-"default_value"}PORT="${PORT:-8080}"
echo "Server listening on port $PORT"Assign Default Value If Unset
If VAR is unset or empty, assigns 'default_value' to VAR and returns it.
${VAR:="default_value"}echo "${APP_ENV:=production}" # Sets APP_ENV to production if blankMandatory Variable Assertion (Error If Unset)
If variable is unset or empty, prints the custom error message to stderr and immediately terminates the script with exit status 1.
${DATABASE_URL:?"Error: DATABASE_URL must be configured": "${AWS_ACCESS_KEY_ID:?Required AWS credentials missing}"String Length
Returns the number of characters in the string contained in VAR.
${#VAR}API_KEY="secret-123"
echo "Key length: ${#API_KEY}" # Output: 10Remove Shortest Prefix Pattern
Strips the shortest matching pattern from the beginning of the string.
${VAR#pattern}FILE="archive.tar.gz"
echo "${FILE#*.}" # Output: tar.gzRemove Longest Prefix Pattern
Strips the longest matching pattern from the beginning of the string (e.g. basename extraction).
${VAR##pattern}PATH_NAME="/usr/local/bin/node"
echo "${PATH_NAME##*/}" # Output: nodeRemove Shortest Suffix Pattern
Strips the shortest matching pattern from the end of the string (e.g. extension removal).
${VAR%pattern}FILENAME="backup.2026.sql"
echo "${FILENAME%.sql}" # Output: backup.2026Remove Longest Suffix Pattern
Strips the longest matching pattern from the end of the string.
${VAR%%pattern}FILE="archive.tar.gz"
echo "${FILE%%.*}" # Output: archiveGlobal Search and Replace
Replaces every occurrence of 'search' pattern with 'replace' string. Use single slash ${VAR/search/replace} to replace only the first occurrence.
${VAR//search/replace}URL="https://example.com/api/v1"
echo "${URL//api/internal}" # Output: https://example.com/internal/v1Convert to Uppercase / Lowercase
Converts all characters in VAR to uppercase (^^) or lowercase (,,) natively in Bash 4+ without external tr or awk calls.
${VAR^^} # UPPERCASE
${VAR,,} # lowercaseROLE="admin"
echo "${ROLE^^}" # Output: ADMINSubstring Extraction (Offset & Length)
Extracts a substring starting at 0-indexed offset for the specified character length.
${VAR:offset:length}HASH="a1b2c3d4e5f6"
echo "${HASH:0:7}" # Output: a1b2c3d (git short sha)Check If String Is Empty or Non-Empty
-z tests for zero length (empty string or unset). -n tests for non-zero length (has content).
if [[ -z "$STR" ]]; then
echo "String is empty"
fi
if [[ -n "$STR" ]]; then
echo "String has content"
fiRegex Pattern Matching in Bash
The '=~' operator performs regex evaluation. Matching groups are stored in the BASH_REMATCH array.
if [[ "$INPUT" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Valid YYYY-MM-DD date"
fiif [[ "$EMAIL" =~ ^([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$ ]]; then
echo "User: ${BASH_REMATCH[1]}, Domain: ${BASH_REMATCH[2]}"
fiFile & Directory Existence Tests
Core file test operators for filesystem inspection.
[[ -f "$FILE" ]] # Regular file exists
[[ -d "$DIR" ]] # Directory exists
[[ -e "$PATH" ]] # File or dir exists
[[ -s "$FILE" ]] # File exists and is NOT empty (> 0 bytes)
[[ -x "$FILE" ]] # File exists and is executable
[[ -L "$LINK" ]] # Symbolic linkNumeric Comparisons in [[ ... ]]
Use integer test operators inside [[ ... ]], or arithmetic syntax (( A > B )).
[[ "$A" -eq "$B" ]] # Equal
[[ "$A" -ne "$B" ]] # Not equal
[[ "$A" -lt "$B" ]] # Less than
[[ "$A" -le "$B" ]] # Less than or equal
[[ "$A" -gt "$B" ]] # Greater than
[[ "$A" -ge "$B" ]] # Greater than or equalCOUNT=5
if (( COUNT >= 5 )); then
echo "Threshold reached"
fiCase Statement (Pattern Matching)
Clean multi-branch conditional matching against strings and glob patterns.
case "$1" in
start|run)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting..."
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esacSafely Read File Line-by-Line (No Trim Traps)
The safest way to process lines. 'IFS=' prevents leading/trailing whitespace trimming; '-r' disables backslash escaping; '[[ -n "$line" ]]' ensures the last line is processed even if it lacks a trailing newline.
while IFS= read -r line || [[ -n "$line" ]]; do
echo "Processing: $line"
done < "data.txt"For Loop Over Array Elements
Iterates over array items. Always use quotes and [@]: "${array[@]}" preserves elements containing spaces.
servers=("web-1" "web-2" "api-1")
for server in "${servers[@]}"; do
echo "Deploying to $server..."
doneNumeric Range For Loop
C-style numeric loop or brace expansion for sequential ranges.
for (( i = 1; i <= 5; i++ )); do
echo "Attempt $i of 5"
done
# Or syntax with braces:
for i in {1..5}; do
echo "Step $i"
doneIterate Over Files Matching Glob Safely
Setting 'nullglob' prevents the loop from executing with the literal string '/var/log/*.log' when no matching files exist.
shopt -s nullglob # avoid literal string if no files match
for file in /var/log/*.log; do
echo "Archiving $file"
done
shopt -u nullglobRetry Command With Exponential Backoff
Repeats a command until it succeeds (exit status 0) or hits the max attempt limit, doubling the delay between tries.
max_attempts=5
attempt=1
delay=2
until curl -sSf "https://api.example.com/health" > /dev/null; do
if (( attempt >= max_attempts )); then
echo "Health check failed after $max_attempts attempts" >&2
exit 1
fi
echo "Attempt $attempt failed. Retrying in ${delay}s..."
sleep "$delay"
(( attempt++ ))
(( delay *= 2 ))
doneIndexed Array Definition & Operations
Indexed arrays in Bash. Always reference with curly braces.
arr=("alpha" "beta" "gamma")
echo "${arr[0]}" # First item (alpha)
echo "${arr[-1]}" # Last item (gamma)
echo "${#arr[@]}" # Array length (3)
echo "${arr[@]}" # All items
arr+=("delta") # Append item
unset 'arr[1]' # Remove element at index 1Associative Array (Key-Value Dictionary in Bash 4+)
Hash map / key-value dictionary support via 'declare -A'. Keys can be arbitrary strings.
declare -A config
config["host"]="127.0.0.1"
config["port"]="5432"
config["db"]="production"
echo "Connecting to ${config["host"]}:${config["port"]}"
# Iterate over keys:
for key in "${!config[@]}"; do
echo "$key = ${config[$key]}"
doneSplit String into Array (Delimited by Comma)
Uses 'read -a' with a custom IFS to parse comma-separated or delimiter-separated strings directly into a Bash array.
CSV="apple,banana,orange,grape"
IFS=',' read -r -a fruits <<< "$CSV"
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
doneJoin Array Elements into Delimited String
Using ${arr[*]} combined with a localized IFS expands the elements joined by the first character of IFS.
arr=("one" "two" "three")
joined=$(IFS=, ; echo "${arr[*]}")
echo "$joined" # Output: one,two,threeFunction Declaration with Local Variables
Declare functions cleanly and always use 'local' for variables inside functions to avoid polluting global state.
log_message() {
local level="${1:-INFO}"
local message="${2:-}"
local timestamp
timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "[$timestamp] [$level] $message" >&2
}
log_message "ERROR" "Database connection timeout"Special Variables Reference Table
Crucial special parameter tokens in shell scripts.
$0 # Name of script
$1..$9 # Positional arguments 1 through 9
${10} # Positional argument 10+ (requires curly braces)
$# # Total count of positional arguments passed
$@ # All arguments as separate quoted words ("$1" "$2" ...)
$* # All arguments joined into single string ("$1 $2 ...")
$? # Exit code of the most recent foreground command
$$ # Process ID (PID) of the current shell script
$! # Process ID (PID) of the last backgrounded jobParse Command-Line Flags (getopts)
Standard POSIX options parser for single-character command flags. Colon after letter (e:) indicates it expects an argument value in $OPTARG.
while getopts "e:p:vh" opt; do
case "$opt" in
e) ENV="$OPTARG" ;;
p) PORT="$OPTARG" ;;
v) VERBOSE=1 ;;
h) echo "Usage: $0 [-e env] [-p port] [-v]"; exit 0 ;;
*) echo "Invalid option" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))Redirect Output & Errors (Cheatsheet)
Standard file descriptor numbers: 0 = stdin, 1 = stdout, 2 = stderr.
command > file # Overwrite stdout to file
command >> file # Append stdout to file
command 2> file # Overwrite stderr to file
command 2>> file # Append stderr to file
command &> file # Redirect both stdout & stderr to file (Bash)
command > file 2>&1 # Portable POSIX redirect stdout & stderr
command > /dev/null 2>&1 # Discard all output completelyHeredoc (Multiline Text & Configuration)
Writes multiline text to a file. Quoting the delimiter 'EOF' prevents variable and command expansion inside the block.
cat << 'EOF' > /tmp/nginx.conf
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
}
}
EOFProcess Substitution (Compare Two Outputs)
Runs commands and presents their output as temporary named file descriptors (/dev/fd/N), enabling commands expecting files to read command output directly.
diff -u <(curl -s "https://api.v1.com/data") <(curl -s "https://api.v2.com/data")Tee (Log to File While Still Displaying in Terminal)
Duplicates stdout stream so you can watch live terminal output while simultaneously saving a full timestamped log.
make build 2>&1 | tee build.logTrap on Script Exit (Automatic Temp Directory Cleanup)
Guarantees cleanup runs whenever the script exits, even if interrupted by error or Ctrl+C. The most robust pattern for temporary resources.
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
# Script operations...
echo "Working in $tmpdir"
# Cleanup runs automatically whether script succeeds or crashes!Standard Linux Exit Codes Meaning
Linux standard exit status codes. Scripts should always return 0 on success and >0 on failure.
0 # Success (no error)
1 # Catchall for general errors
2 # Misuse of shell built-ins (syntax errors)
126 # Command invoked cannot execute (permissions problem)
127 # Command not found (PATH issue or typo)
128 # Invalid argument to exit
130 # Script terminated by Ctrl+C (SIGINT = 128 + 2)
137 # Script terminated by SIGKILL (128 + 9, e.g. Linux OOM killer)
143 # Script terminated by SIGTERM (128 + 15)Prompt User for Confirmation [y/N]
Interactive confirmation guard that defaults safely to No if the user presses Enter without typing 'y'.
read -r -p "Are you sure you want to deploy to production? [y/N] " response
case "$response" in
[yY][eE][sS]|[yY])
echo "Proceeding..."
;;
*)
echo "Aborted by user."
exit 1
;;
esacRelated Developer & DevOps Tools
Supercharge your terminal workflow with complementary client-side utilities from the DevToolkit Hub catalog:
Frequently Asked Questions About Bash Scripting
Why should every Bash script start with 'set -euo pipefail'?
'set -euo pipefail' is commonly known as unofficial Bash Strict Mode. '-e' causes the script to abort immediately if any command exits with a non-zero status. '-u' throws an error and halts execution if an unset variable is referenced, preventing catastrophic bugs like 'rm -rf $DIR/*' when $DIR is typoed. '-o pipefail' ensures that pipelines return the exit code of the last failing command in the pipe rather than masking errors when the final command succeeds. Finally, setting 'IFS=$'\n\t'' prevents unintended word splitting on spaces.
What is the difference between single brackets [ ... ] and double brackets [[ ... ]] in Bash?
Single brackets '[ ... ]' invoke the POSIX standard test utility, which is subject to pathname expansion and word splitting, requiring extensive quoting. Double brackets '[[ ... ]]' are a native Bash keyword enhancement that prevents word splitting, supports regular expression matching via the '=~' operator, and allows logical '&&' and '||' operators directly inside the conditional.
How do I safely read a text file line-by-line without trimming whitespace?
Use 'while IFS= read -r line || [[ -n "$line" ]]; do ... done < file.txt'. Clearing IFS ('IFS=') stops leading and trailing whitespace from being stripped. The '-r' flag disables backslash interpretation. Checking '[[ -n "$line" ]]' ensures the final line of the file is processed even if it does not end with a trailing newline.
What does ${VAR:-default} mean compared to ${VAR:=default}?
'${VAR:-default}' evaluates and returns 'default' if VAR is empty or unset, leaving VAR unmodified. In contrast, '${VAR:=default}' evaluates 'default' and simultaneously assigns it into VAR, persisting the value for subsequent commands in the script.
How do I clean up temporary files reliably when a Bash script finishes or crashes?
Use the 'trap' built-in targeting the EXIT pseudo-signal. For example: 'tmpdir=$(mktemp -d); trap 'rm -rf "$tmpdir"' EXIT'. This guarantees that the removal command is triggered automatically whenever the script exits, whether it terminates cleanly, errors out under 'set -e', or receives an interruption signal.