Skip to main content

Bash Cheatsheet — 100+ Commands and Idioms for Shell Scripting on Linux and macOS

Bash cheat sheet — 100+ commands & idioms for variables, conditionals, loops, functions, pipes, traps, with real one-liners.

  • Runs locally
  • Category Developer & DevOps
  • Best for Formatting, validating, shrinking, or inspecting code-adjacent text.
Section:
113 commands
Variables (16)
name=value

Assign a variable. NO spaces around `=` — `name = value` is a command call, not an assignment.

Common pitfall: `x = 1` calls a program named `x` with args `=` and `1`. Bash will say `x: command not found` and you will spend ten minutes wondering why.

Examples
name="Lei Li"
count=42
today=$(date +%F)
export NAME=value

Mark a variable for export to child processes. Without `export`, the variable stays in the current shell only.

Examples
export PATH="$HOME/bin:$PATH"
export EDITOR=vim
"${var}"

Quoted expansion with explicit braces. The braces let you stick text right after the variable name without ambiguity.

Common pitfall: Without braces, `$prefix_v1` looks like the variable `prefix_v1`, not `$prefix` followed by `_v1`. Write `"${prefix}_v1"`.

Examples
echo "Hello, ${name}!"
file="${base}_${date}.log"
${var:-default}

Use $var if set and non-empty; otherwise substitute `default`. Does NOT modify $var.

Examples
port="${PORT:-8080}"
name="${1:-world}"   # default first arg
${var:=default}

Same as `:-` but also ASSIGNS the default back into $var. Does not work on positional args ($1, $2…).

Examples
: "${LOG_DIR:=/var/log/app}"   # ensure LOG_DIR is set
${var:?error message}

If $var is unset or empty, print the message to stderr and exit. Great as a hard guard at the top of a script.

Examples
: "${API_KEY:?need to set API_KEY in env}"
${var:+alt}

If $var is set and non-empty, substitute `alt`; otherwise substitute nothing. Useful for "only add this flag if X was set".

Examples
args="${DEBUG:+--verbose}"   # add --verbose only if DEBUG is set
${var//old/new}

Replace EVERY occurrence of `old` with `new` in $var. Single slash replaces only the first.

Examples
path="${path//\\//}"   # backslashes to forward slashes
safe="${name// /_}"
${var#prefix} / ${var##prefix}

Strip the shortest (#) or longest (##) matching `prefix` from the front of $var. Patterns are globs, not regex.

Examples
file=/path/to/file.tar.gz
echo "${file##*/}"   # file.tar.gz
echo "${file#*/}"    # path/to/file.tar.gz
${var%suffix} / ${var%%suffix}

Strip the shortest (%) or longest (%%) matching `suffix` from the end of $var. Mirror of # and ##.

Examples
file=archive.tar.gz
echo "${file%.gz}"     # archive.tar
echo "${file%%.*}"     # archive
${#var}

Length (in characters) of $var.

Examples
s="hello"
echo "${#s}"   # 5
${var:start:length}

Substring of $var: skip `start` chars, take `length` chars. Both numbers can be expressions.

Examples
s="abcdefgh"
echo "${s:2:3}"   # cde
echo "${s:(-3)}"  # fgh (last 3)
${var^^} / ${var,,}

Upper-case (^^) or lower-case (,,) all characters of $var. Bash 4+ only.

Common pitfall: macOS ships Bash 3.2 (license reasons). On macOS you need `brew install bash` to get 4+, or fall back to `tr`.

Examples
s="hello"
echo "${s^^}"   # HELLO
echo "${s,,}"   # hello (already)
$RANDOM

Bash built-in that yields a random integer 0–32767 each time it is referenced.

Examples
echo $RANDOM
echo $((RANDOM % 100))   # 0-99
tmp="/tmp/job-$RANDOM"
readonly NAME=value

Declare a constant — any later assignment to NAME will fail. Use for config you really do not want overwritten.

Examples
readonly VERSION=1.2.3
readonly LOG_DIR=/var/log/app
unset name

Delete a variable entirely. After `unset x`, both `$x` and `${x:-}` expand to empty.

Examples
unset TMPDIR
unset -f my_function   # also unsets a function
Conditionals (12)
if [[ condition ]]; then ... fi

Bash conditional. Prefer `[[ ]]` over the older `[ ]` — it handles spaces, regex, and globbing without surprises.

Examples
if [[ -f config.yaml ]]; then echo "found"; fi
if [[ "$user" == "root" ]]; then echo "boss"; fi
if [[ "$a" == "$b" ]]

String equality. `==` and `=` both work inside `[[ ]]`. Always quote the right side unless you want glob matching.

Common pitfall: Inside `[[ ]]`, an UNQUOTED right side becomes a glob: `[[ $f == *.log ]]` matches any file ending in .log. Quote it to compare literally.

Examples
if [[ "$name" == "alice" ]]; then ... fi
if [[ "$file" == *.log ]]; then ...   # glob match, no quotes
if [[ "$a" != "$b" ]]

String inequality. Symmetric to `==`.

Examples
if [[ "$env" != "prod" ]]; then echo "safe to delete"; fi
if [[ "$s" =~ regex ]]

Regex match. The regex is bash ERE; captures land in BASH_REMATCH.

Common pitfall: Do NOT quote the regex — quoting turns metacharacters into literals. `[[ $s =~ "^[0-9]+$" ]]` matches the literal string, not digits.

Examples
if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "looks like ipv4"; fi
if [[ "$s" =~ ^([a-z]+)=([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}"; fi
if (( a < b ))

Arithmetic comparison. Use `(( ))` for numeric work — `<`, `>`, `==`, `!=`, `<=`, `>=` all behave like C.

Examples
if (( count > 10 )); then echo "many"; fi
if (( a + b == 100 )); then echo "match"; fi
[[ "$a" -lt "$b" ]]

POSIX-style numeric test inside `[[ ]]`. Operators: -lt -le -gt -ge -eq -ne. Use these when comparing numbers as strings.

Common pitfall: `[[ "5" > "10" ]]` is TRUE — string comparison says "5" sorts after "1". Use `-gt` or `(( 5 > 10 ))` for numeric intent.

Examples
if [[ "$age" -ge 18 ]]; then echo "adult"; fi
[[ -f file ]] / [[ -d dir ]] / [[ -e path ]]

File tests. -f: regular file. -d: directory. -e: any path exists (file, dir, socket, link target). -L: symlink.

Examples
[[ -f /etc/hosts ]] && echo "exists"
[[ -d "$HOME/.config" ]] || mkdir -p "$HOME/.config"
[[ -e /tmp/lock ]] && exit 1
[[ -z "$s" ]] / [[ -n "$s" ]]

-z: string is empty (zero length). -n: string is non-empty.

Common pitfall: ALWAYS quote: `[[ -z $s ]]` breaks if $s contains spaces. `[[ -z "$s" ]]` is safe.

Examples
[[ -z "$NAME" ]] && NAME="anon"
if [[ -n "$DEBUG" ]]; then set -x; fi
cmd && echo ok || echo fail

Short-circuit chain. && runs the next command only if the previous succeeded (exit 0); || only if it failed.

Common pitfall: `cmd && a || b` is NOT `if cmd then a else b`. If `a` itself fails, `b` runs too. Use a real `if` for branching.

Examples
ping -c1 host && echo "up" || echo "down"
[[ -d build ]] || mkdir build
if ! cmd; then ... fi

Negate a command exit status. `!` flips success/failure for use in `if` / `while`.

Examples
if ! grep -q "ok" status.log; then echo "missing ok line"; fi
while ! curl -fs http://app/health; do sleep 1; done
case "$var" in pattern) ... ;; esac

Multi-way branch on glob patterns. Cheaper and clearer than a tower of elif when matching strings.

Examples
case "$1" in
  start) start_service ;;
  stop)  stop_service ;;
  *)     echo "usage: $0 start|stop"; exit 1 ;;
esac
[[ "$a" && "$b" ]]

Logical AND inside a single `[[ ]]`. Use `&&` (not `-a`) — `[[ ]]` is bash, not POSIX test.

Examples
if [[ -f config && -r config ]]; then echo "readable"; fi
Loops (11)
for x in a b c; do ... done

Loop over a literal list of words. Each iteration assigns one word to $x.

Examples
for env in dev staging prod; do
  echo "deploying $env"
done
for f in *.log; do ... done

Loop over filenames matching a glob. Handles spaces correctly — no word-splitting on whitespace inside filenames.

Common pitfall: If no files match, the glob expands to the literal pattern `*.log` and your loop body sees `f="*.log"`. Guard with `shopt -s nullglob` to skip the loop on no matches.

Examples
shopt -s nullglob
for f in *.log; do
  gzip "$f"
done
for ((i=0; i<10; i++)); do ... done

C-style for loop. Use for indexed iteration when you need the counter itself.

Examples
for ((i=1; i<=5; i++)); do
  echo "attempt $i"
done
for x in $(cmd); do ... done

Iterate over words from command output. Output is split on $IFS (default: spaces, tabs, newlines).

Common pitfall: Filenames with spaces break this. Prefer `while read -r line` with redirection for lines, or set IFS=$'\n' for newline-only splitting.

Examples
for pid in $(pgrep nginx); do
  echo "nginx pid: $pid"
done
while read -r line; do ... done < file

Read a file line by line, safely. `-r` disables backslash escapes; the < at the end feeds the loop without subshell traps.

Common pitfall: If you write `cat file | while read line`, the loop runs in a subshell and variables you set inside DIE when the loop ends. Use `done < file` instead.

Examples
while read -r line; do
  echo "got: $line"
done < urls.txt
while [[ condition ]]; do ... done

Loop while a condition stays true. Common for retry loops and queue draining.

Examples
attempts=0
while ! curl -fs http://app/health; do
  ((attempts++))
  (( attempts > 30 )) && exit 1
  sleep 1
done
until [[ condition ]]; do ... done

Loop UNTIL the condition becomes true (i.e. while it is false). Symmetric to `while`.

Examples
until ping -c1 -W1 host >/dev/null 2>&1; do sleep 1; done
break / continue

`break` exits the innermost loop right now. `continue` skips to the next iteration. `break 2` exits two levels.

Examples
for f in *; do
  [[ -d "$f" ]] || continue
  [[ "$f" == "stop" ]] && break
  echo "$f"
done
select x in a b c; do ... done

Interactive numbered menu. Bash prints the choices, the user picks a number, $x gets the value. Loops until you `break`.

Examples
select env in dev staging prod quit; do
  [[ "$env" == "quit" ]] && break
  echo "you chose $env"
done
for x in {1..10}; do ... done

Brace expansion gives a numeric range. {1..10..2} skips by 2. Expanded BEFORE variable substitution.

Common pitfall: `{1..$n}` does NOT work because brace expansion happens before $n is read. Use `seq $n` or a C-style for loop.

Examples
for i in {1..5}; do echo "$i"; done
for i in {0..100..10}; do echo "$i"; done
while IFS=, read -r a b c; do ... done < file.csv

Parse a CSV line by line: set IFS to comma, then read splits the line into named fields.

Examples
while IFS=, read -r name age role; do
  echo "$name ($age) is a $role"
done < team.csv
Functions (8)
name() { ...; }

Define a function. The `function` keyword is optional in bash; the POSIX form `name()` is more portable.

Examples
greet() {
  echo "Hello, $1!"
}
greet "Lei"   # → Hello, Lei!
function name { ...; }

Bash-only function form. Identical to `name() { ... }` but with the keyword.

Examples
function deploy {
  echo "deploying $1 to $2"
}
$1 $2 ... $9 ${10}

Positional arguments inside a function or script. $1 is the first, $0 is the script/function name.

Common pitfall: Two-digit args need braces: `$10` reads as `$1` followed by literal `0`. Use `${10}` for the tenth arg.

Examples
copy() {
  cp "$1" "$2"
}
"$@" vs "$*"

"$@" preserves each argument as a separate word (the usual right answer). "$*" joins them with the first char of $IFS.

Common pitfall: Without quotes, both behave the same — and both break on spaces. ALWAYS use the quoted form `"$@"` when forwarding args.

Examples
run_with_logging() {
  echo "[$(date)] running: $*"
  "$@"
}
local var=value

Declare a function-local variable. Without `local`, every assignment in a function pollutes the global scope.

Examples
greet() {
  local who="$1"
  echo "Hello, $who"
}
return N

Set the function exit status. 0 = success, 1-255 = failure. NOT the same as a function "return value" — for data use stdout + $(fn).

Examples
is_valid() {
  [[ "$1" =~ ^[0-9]+$ ]] && return 0 || return 1
}
if is_valid "$x"; then echo ok; fi
result=$(my_function arg1 arg2)

Capture function output. Functions return data the same way commands do: print to stdout, caller wraps in $(...).

Examples
get_timestamp() { date +%s; }
now=$(get_timestamp)
echo "now is $now"
declare -f name

Print the definition of a function. `declare -F` lists names only. Useful for debugging which version of a function is loaded.

Examples
declare -f greet
declare -F | head   # list all defined functions
Redirection (10)
cmd > file

Redirect stdout to file, OVERWRITING. Use `>>` to append instead.

Examples
ls > files.txt
date >> log.txt   # append
cmd 2> file

Redirect stderr (fd 2) to file. stdout still goes to the terminal.

Examples
make 2> build-errors.log
cmd 2>/dev/null   # discard errors
cmd > file 2>&1

Redirect BOTH stdout and stderr to the same file. Order matters: first redirect stdout, THEN dup stderr to wherever stdout now points.

Common pitfall: `cmd 2>&1 > file` does NOT work — stderr is duped to the terminal first, then stdout is moved to file. Always put `2>&1` AFTER `> file`.

Examples
build.sh > build.log 2>&1
cmd &> file   # bash shortcut for the same thing
cmd &> file

Bash shortcut: redirect both stdout AND stderr to file. Equivalent to `> file 2>&1`.

Common pitfall: `&>` is bash-specific. POSIX sh does not understand it — for portable scripts use `> file 2>&1`.

Examples
./test.sh &> test.log
cmd < file

Feed file as stdin to cmd. Same as `cat file | cmd` but without the extra cat process.

Examples
mysql -u root db < schema.sql
while read -r line; do echo "$line"; done < input.txt
cmd <<EOF ...text... EOF

Here-document: feed multi-line literal text as stdin. The marker (EOF here) just has to be unique on its own line.

Examples
cat <<EOF > config.yaml
name: app
port: 8080
EOF
cmd <<'EOF' ... EOF

Quoted heredoc — single-quoting the marker DISABLES variable and command substitution inside. The body is fully literal.

Examples
cat <<'EOF'
This $will not expand and `nor will this`.
EOF
cmd <<<"$var"

Here-string: feed a single string as stdin. Cleaner than `echo "$var" | cmd` for one-liners.

Examples
grep -c "ERROR" <<< "$log"
tr a-z A-Z <<< "hello"
exec > file

Redirect the SCRIPT's own stdout from this line on. After this, every command in the script writes to `file` instead of the terminal.

Examples
#!/bin/bash
exec > script.log 2>&1
echo "this goes to script.log"
# tee pattern:
exec > >(tee script.log) 2>&1
cmd 2>/dev/null

Throw stderr away. Useful when a command spews known noise — e.g. `find` complaining about permission-denied dirs.

Common pitfall: Hiding errors hides real bugs. Use sparingly and prefer filtering specific patterns: `cmd 2> >(grep -v "expected noise" >&2)`.

Examples
find / -name foo 2>/dev/null
which gcc &>/dev/null && echo "have gcc"
Pipes & subshells (10)
cmd1 | cmd2

Pipe: stdout of cmd1 becomes stdin of cmd2. Each command runs in its own subprocess in parallel.

Examples
ps aux | grep nginx
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn
cmd1 |& cmd2

Pipe both stdout AND stderr into cmd2. Bash 4+ shortcut for `cmd1 2>&1 | cmd2`.

Examples
make |& tee build.log
cmd &

Run cmd in the background. Returns immediately with the PID in $!. `wait` blocks until all background jobs finish.

Examples
long_task &
pid=$!
# do other work
wait $pid
$(cmd)

Command substitution: replace with cmd's stdout. Preferred over backticks because $(...) NESTS cleanly.

Common pitfall: Trailing newlines are STRIPPED. If you care about preserving them, append a sentinel: `out=$(cmd; echo END)`.

Examples
today=$(date +%F)
count=$(grep -c ERROR app.log)
files=$(find . -name "*.py" | head -5)
`cmd`

Old-style command substitution. Works but does NOT nest cleanly and quoting gets ugly. Prefer `$(cmd)`.

Examples
date=`date +%F`   # works but legacy
echo "Today: $(date +%F)"   # better
<(cmd)

Process substitution: makes cmd's output look like a temp filename. Lets you pass "streams" where a tool expects file arguments.

Common pitfall: Process substitution is bash, not POSIX sh. The `<(...)` syntax errors out under dash / busybox sh.

Examples
diff <(sort a.txt) <(sort b.txt)
comm -12 <(sort users-old) <(sort users-new)
set -o pipefail

Make a pipeline fail if ANY command in the pipe fails. Without this, only the LAST command's exit code is reported.

Examples
set -euo pipefail
curl -fsS bad-url | jq .   # without pipefail, jq exit 0 hides curl failure
cmd | tee file

Pipe to file AND keep printing to stdout. Add `-a` to append instead of overwrite.

Examples
make 2>&1 | tee build.log
echo "192.168.1.1 host" | sudo tee -a /etc/hosts
( cmd1; cmd2 )

Run a group in a SUBSHELL. Variables you set inside do NOT leak out — useful for scoping `cd`, `set -x`, env tweaks.

Examples
( cd /tmp && tar xzf "$tarball" )   # the parent shell stays where it was
{ cmd1; cmd2; }

Run a group in the CURRENT shell (no subshell). Variables persist after the group. Needs spaces and trailing semicolon.

Examples
{ echo "header"; cat file; echo "footer"; } > out.txt
Strings (6)
echo -n "$var" | wc -c

Byte length of $var (use `${#var}` for character length in single-byte locales).

Common pitfall: `${#var}` counts characters not bytes — a Chinese character may be 1 in count but 3 bytes in UTF-8. `wc -c` counts bytes; `wc -m` counts characters in the current locale.

Examples
s="hello"
echo "${#s} chars"
echo -n "$s" | wc -c
IFS=":" read -ra parts <<< "$path"

Split a string by a delimiter into an array. `-a parts` reads into the indexed array `parts`.

Examples
IFS=":" read -ra dirs <<< "$PATH"
for d in "${dirs[@]}"; do echo "$d"; done
printf -v var "format" args

Assign formatted output directly to a variable — no subshell, no command substitution overhead.

Examples
printf -v fname "report-%s-%02d.csv" "$dept" "$month"
echo "$fname"   # report-sales-05.csv
echo "${s// /_}"

Replace all spaces with underscores. Single slash replaces only the first match.

Examples
name="hello world"
safe="${name// /_}"
echo "$safe"   # hello_world
[[ "$s" == prefix* ]]

Test if a string starts with a prefix using glob in `[[ ]]`. Suffix is `*prefix`.

Examples
if [[ "$file" == /etc/* ]]; then echo "system config"; fi
if [[ "$url" == https://* ]]; then ...; fi
tr "a-z" "A-Z" <<< "$s"

Upper-case a string portably (works on bash 3.2 macOS). tr does character translation.

Examples
upper=$(tr "a-z" "A-Z" <<< "$name")
echo "$upper"
Flow control (9)
set -e

Exit immediately if any command returns non-zero. The "fail fast" flag — combine with -u and -o pipefail for safe scripts.

Common pitfall: set -e is FULL of corner cases. `cmd1 && cmd2` does NOT trigger -e if cmd1 fails. `cmd || true` is the idiom for "I expect this to fail sometimes".

Examples
set -euo pipefail
IFS=$'\n\t'   # the "unofficial bash strict mode" header
set -u

Treat unset variables as errors. Catches typos like `$ptah` vs `$path` immediately instead of silently expanding to "".

Common pitfall: With -u, `${var}` errors out if unset. Always use `${var:-}` for "I know it might be unset" and `${var:?msg}` to assert it must be set.

Examples
set -u
echo "$undefined"   # bash: undefined: unbound variable
set -x

Print every command (with expanded variables) before running it. The fastest way to debug a script.

Examples
set -x
name="lei"
echo "hello $name"
# stderr: + name=lei
#         + echo 'hello lei'
set -o pipefail

A pipeline's exit code is the rightmost FAILING command, not just the last command. Pairs with -e.

Examples
set -o pipefail
curl bad-url | jq .   # now fails properly when curl fails
trap "cleanup" EXIT

Run `cleanup` when the script exits, no matter how (normal, error, signal). The standard pattern for tmpfile cleanup.

Examples
tmp=$(mktemp)
trap "rm -f \"$tmp\"" EXIT
# tmp gets removed even if the script crashes
trap "..." INT TERM

Intercept Ctrl-C (INT) and `kill` (TERM). Print a message, do cleanup, then exit gracefully.

Examples
trap "echo '^C — bye'; exit 130" INT
trap "stop_server; exit" TERM
exit N

Terminate the script with exit code N. 0 = success. By convention 1 = generic error, 2 = misuse, 127 = command not found.

Examples
exit 0
exit 1   # generic failure
[[ $# -eq 0 ]] && { echo "need an arg"; exit 2; }
$?

Exit status of the LAST command. Read it ONCE — every later command (including `[ ]`) updates it.

Common pitfall: `cmd; if [[ $? -eq 0 ]]; then echo "ok"; fi` is over-engineered. Just write `if cmd; then echo "ok"; fi`.

Examples
ping -c1 host >/dev/null
rc=$?
echo "ping returned $rc"
time cmd

Print how long cmd took (real, user, sys). Built-in to bash; works on pipelines too.

Examples
time make build
time (find /usr | wc -l)
Script patterns (11)
#!/usr/bin/env bash

Shebang line. `env bash` finds bash on $PATH instead of hard-coding /bin/bash (which on macOS is the ancient 3.2 build).

Examples
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
while getopts ":hf:v" opt; do ... done

POSIX-portable option parsing. `:` after a letter means it takes an argument (in $OPTARG); leading `:` enables silent error handling.

Examples
while getopts ":hf:v" opt; do
  case $opt in
    h) echo "usage: ..."; exit 0 ;;
    f) file="$OPTARG" ;;
    v) verbose=1 ;;
    \?) echo "bad flag: -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))   # consume parsed flags
while [[ $# -gt 0 ]]; do case "$1" in ... esac; done

Hand-rolled arg parsing for long options (--flag value, --flag=value). More flexible than getopts but you write more code.

Examples
while [[ $# -gt 0 ]]; do
  case "$1" in
    --port)  port="$2"; shift 2 ;;
    --port=*) port="${1#*=}"; shift ;;
    --) shift; break ;;
    *) args+=("$1"); shift ;;
  esac
done
tmp=$(mktemp); trap "rm -f \"$tmp\"" EXIT

Create a guaranteed-unique temp file and arrange to delete it on exit. Use `mktemp -d` for a temp directory.

Common pitfall: BSD mktemp (macOS) needs a template: `mktemp /tmp/myapp.XXXXXX`. GNU mktemp works without one. `mktemp -t prefix` is the portable form.

Examples
tmp=$(mktemp)
trap "rm -f \"$tmp\"" EXIT
curl -o "$tmp" https://example.com/data.json
echo -e "\033[31mred\033[0m"

Print colored text via ANSI escape codes. \033[31m = red, \033[0m = reset. Only when output is a TTY (test with `[[ -t 1 ]]`).

Examples
RED=$'\033[31m'
RESET=$'\033[0m'
[[ -t 1 ]] || RED='' RESET=''   # disable colors when piped
echo "${RED}ERROR${RESET}: bad config"
progress() { printf "\r[%-50s] %d%%" "$bar" "$pct"; }

A poor-man's progress bar. \r returns the cursor to the line start so each update overwrites the previous one.

Examples
for i in {1..100}; do
  pct=$i
  bar=$(printf "%.0s#" $(seq 1 $((pct/2))))
  printf "\r[%-50s] %3d%%" "$bar" "$pct"
  sleep 0.05
done
echo
echo "$0"

Script's own invocation name. Useful for usage strings: `echo "usage: $0 [options]"`.

Examples
if [[ $# -lt 2 ]]; then
  echo "usage: $0 <src> <dst>" >&2
  exit 2
fi
BASH_SOURCE / readlink -f

Resolve the script's own directory, even when called through symlinks. The standard "find my siblings" idiom.

Examples
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib.sh"
command -v cmd >/dev/null

Check whether a command exists without running it. Returns 0 if found. More portable than `which`.

Examples
if ! command -v jq >/dev/null; then
  echo "need jq, please install" >&2
  exit 1
fi
read -p "Continue? [y/N] " ans

Prompt the user and read one line into $ans. -p sets the prompt; -s hides input (for passwords).

Examples
read -p "Continue? [y/N] " ans
[[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] || exit 0
log() { echo "[$(date +%H:%M:%S)] $*" >&2; }

Cheap timestamped log helper that writes to stderr (so it does not pollute pipeline output).

Examples
log() { echo "[$(date +%H:%M:%S)] $*" >&2; }
log "starting deploy"
log "done"
Arrays (9)
declare -a arr

Declare an indexed (numeric-key) array. Bash arrays are 0-indexed.

Examples
declare -a fruits=(apple banana cherry)
echo "${fruits[0]}"   # apple
declare -A map

Declare an associative array (string keys). Bash 4+ only. macOS Bash 3.2 does NOT support these.

Common pitfall: You MUST `declare -A map` before using it. Otherwise `map["key"]="v"` silently treats `"key"` as 0 and you get an indexed array with one element.

Examples
declare -A color
color[apple]=red
color[grape]=purple
echo "${color[apple]}"   # red
arr=(a b c)

Initialize an indexed array literally. Space-separated, parens around.

Examples
envs=(dev staging prod)
echo "${envs[1]}"   # staging
arr+=(new1 new2)

Append elements to the end of an indexed array.

Examples
files=()
for f in *.log; do files+=("$f"); done
echo "${#files[@]} log files"
"${arr[@]}"

Expand to ALL elements as separate quoted words. The usual right way to iterate.

Common pitfall: `"${arr[*]}"` joins everything into ONE string with $IFS. Almost always you want `"${arr[@]}"` instead.

Examples
for f in "${files[@]}"; do
  echo "$f"
done
"${#arr[@]}"

Number of elements in the array.

Examples
echo "${#files[@]} files matched"
if (( ${#errors[@]} > 0 )); then echo "had errors"; fi
"${!arr[@]}"

Expand to all KEYS of the array (indices for indexed arrays, string keys for associative).

Examples
for i in "${!arr[@]}"; do
  echo "$i -> ${arr[$i]}"
done
for k in "${!color[@]}"; do
  echo "$k is ${color[$k]}"
done
unset "arr[2]"

Remove a single element. Indices are NOT renumbered — `${#arr[@]}` shrinks but `${arr[3]}` is still there.

Examples
arr=(a b c d)
unset "arr[1]"
echo "${arr[@]}"   # a c d
echo "${!arr[@]}"   # 0 2 3
mapfile -t arr < file

Read every line of a file into an array, one line per element. `-t` strips the trailing newline from each line.

Examples
mapfile -t lines < urls.txt
echo "read ${#lines[@]} urls"
Common pitfalls (11)
Always quote your variables

Unquoted variable expansion goes through word-splitting (split on $IFS) AND glob expansion. A filename with spaces or a glob char becomes multiple arguments.

Common pitfall: `rm $file` with file="report 2024.csv" becomes `rm report 2024.csv` — two args. `rm "$file"` is the safe form. Default to quoting; un-quote only when you specifically want splitting.

Examples
cp "$src" "$dst"   # safe
for f in "$@"; do echo "$f"; done   # safe
cd may fail — guard with &&

`cd /missing/dir; rm -rf *` deletes the CURRENT directory if cd fails. Always chain: `cd dir && rm ...` or check `cd || exit`.

Common pitfall: set -e does NOT help with this — `cd` failing followed by `;` rm runs anyway. Use && or `cd dir || { echo "no such dir" >&2; exit 1; }`.

Examples
cd "$build_dir" && rm -rf *   # safe
pushd "$dir" || exit; do_work; popd
Pipe into while loop runs in a subshell

`cat file | while read line; do count=$((count+1)); done` — $count is 0 after the loop because while ran in a subshell. Use `done < file` or process substitution.

Examples
# wrong:
# cat file | while read l; do n=$((n+1)); done
# right:
while read -r l; do n=$((n+1)); done < file
echo "$n"
set -e does not catch every failure

set -e ignores failures inside `if`, `while`, `until`, `&&`, `||`, and pipelines (without pipefail). It only kills on bare commands at the top level.

Common pitfall: Add `set -o pipefail` and never rely on -e as your only safety net. Explicit checks (`|| { echo err; exit 1; }`) are the real defense.

Examples
set -euo pipefail
critical_cmd || { echo "critical failed" >&2; exit 1; }
globbing surprises (no match)

A glob with no matches expands to the LITERAL pattern, not an empty list. `for f in *.log` with no logs iterates once with f="*.log".

Common pitfall: Fix with `shopt -s nullglob` (no-match → empty list) or `shopt -s failglob` (no-match → error). Pick once per script and stick with it.

Examples
shopt -s nullglob
for f in *.log; do echo "$f"; done
wc -l misses the last line without a newline

`wc -l` counts NEWLINE characters, not lines. A file ending without \n loses its last line in the count.

Common pitfall: Use `awk "END{print NR}"` for a true line count, or `grep -c ""` which counts records including the unterminated one.

Examples
printf "a\nb" | wc -l   # 1 (!)
printf "a\nb" | awk "END{print NR}"   # 2
Arithmetic vs string in [[ ]]

`[[ "5" > "10" ]]` is TRUE because it does ASCII comparison. For numbers use `(( 5 > 10 ))` or `[[ 5 -gt 10 ]]`.

Examples
(( age >= 18 ))   # numeric
[[ "$name" > "alice" ]]   # alphabetic
$_ vs $0 in sourced scripts

In a sourced script, `$0` is the SHELL's name (bash, zsh), not the script's. Use `${BASH_SOURCE[0]}` to get the actual file path.

Examples
# inside lib.sh:
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  echo "run directly"
else
  echo "sourced from $0"
fi
macOS bash is stuck at 3.2

Apple ships bash 3.2 (May 2007) because newer versions are GPLv3-licensed. Associative arrays, ${var^^}, mapfile, |& all need bash 4+.

Common pitfall: `brew install bash` then put `/opt/homebrew/bin/bash` in your shebang. Or change to zsh, which is default on modern macOS.

Examples
bash --version   # check yours
#!/opt/homebrew/bin/bash   # macOS modern bash
Subshell variable scope

Parentheses `(...)` run in a subshell — variable assignments and `cd` inside do NOT affect the parent shell.

Examples
x=outer
(x=inner; echo "in: $x")   # in: inner
echo "out: $x"             # out: outer
getopts cannot do long options

Built-in `getopts` only handles single-char flags like `-f file`. For `--file file` you need `getopt` (note: no s — different program) or hand-rolled parsing.

Common pitfall: GNU getopt and BSD getopt are incompatible. macOS getopt is the original BSD one — no long-option support. For cross-platform long flags, hand-roll the loop.

Examples
# portable long-option parsing:
while [[ $# -gt 0 ]]; do
  case "$1" in
    --file) file="$2"; shift 2 ;;
    --) shift; break ;;
    *) shift ;;
  esac
done

What this tool does

Searchable Bash cheat sheet with 100+ commands and idioms, organized into the eleven sections you actually reach for at 2am. Variables: "${var}", defaults ${var:-x}, assign-if-unset ${var:=x}, error ${var:?msg}, strip ${var#…} ${var%…}, replace ${var//old/new}, length ${#var}, substring ${var:0:5}, case ${var^^} ${var,,}. Conditionals: [[ ]], string and glob equality, =~ regex with BASH_REMATCH, arithmetic (( )), -lt -gt -eq, file tests -f -d -e -L -s, -z -n, && ||, case. Loops: for over lists and globs, C-style for ((i=0;i<n;i++)), the safe while read -r line < file pattern, until, select, break / continue, {1..10}. Functions: name() and function forms, $1 ${10}, "$@" vs "$*", local, return, capture with x=$(fn). Redirection: > >> 2> 2>&1 &> < <<EOF <<'EOF' <<<, exec for the script itself, /dev/null. Pipes: | |& & with $!, $(cmd), <(cmd), set -o pipefail, tee, ( ) subshell vs { } group. Strings: byte vs character length, IFS-split into arrays, printf -v, glob prefix tests. Flow: set -e -u -x, pipefail, trap EXIT for cleanup, trap INT TERM for signals, exit N, $?, time. Script patterns: shebang, getopts, hand-rolled long options, mktemp + trap, ANSI colors with TTY detection, progress bar, BASH_SOURCE, command -v, read -p, stderr logging. Arrays: declare -a / -A, +=(), "${arr[@]}", "${#arr[@]}", "${!arr[@]}", unset, mapfile. Pitfalls: always quote, cd may fail (use && not ;), pipe-into-while runs in a subshell so counters die, set -e misses failures in if / && / pipelines without pipefail, unmatched glob becomes the literal pattern (nullglob), wc -l counts newlines so the last unterminated line is missed, [[ "5" > "10" ]] is true (string compare), $0 vs ${BASH_SOURCE[0]} in sourced scripts, macOS ships bash 3.2 (brew install bash for 4+), getopts can't do long options. Every entry has bilingual text, a copy-ready example, and a pitfall callout where it matters. Search filters across command, description, pitfall, and example; one-click copy.

Tool details

Input
Form fields
The page exposes text boxes, numeric controls, file pickers, or structured inputs depending on the tool.
Output
Live result + Copy
The result area focuses on usable output, with copy, download, or preview actions when supported.
Privacy
Browser-side processing
The main tool logic does not call an external API, so inputs normally stay in the current tab.
Save / share
No account required
Open the page and use it; whether results survive refresh depends on the tool.
Performance budget
Initial JS <= 28 KB
No WASM budget is declared, keeping the tool quick to open on mobile.
Best fit
Developer & DevOps · Developer
Category and role tags drive related tools, internal links, and quick fit checks.

How to use

  1. 1. Input

    Paste or drop your content into the tool panel.

  2. 2. Process

    Click the button. All processing is local in your browser.

  3. 3. Copy / Download

    Copy the result or download to disk in one click.

How Bash Cheatsheet fits into your work

Use it in the small gaps between coding, reviewing, debugging, and shipping.

Developer jobs

  • Formatting, validating, shrinking, or inspecting code-adjacent text.
  • Preparing snippets for documentation, tickets, commits, or handoff.
  • Checking a small payload quickly without switching tools.

Developer checks

  • Run irreversible transforms like minify or obfuscate on a copy.
  • Keep secrets out of pasted snippets unless the tool explicitly stays local.
  • Use your normal tests or linter before shipping transformed code.

Good next steps

These links move the current task into a more complete workflow.

  1. 1 Nginx Cheatsheet Nginx cheat sheet — common configs, location/server blocks, SSL, reverse proxy, gzip, real examples & gotchas. Open
  2. 2 awk + sed Cheatsheet awk + sed cheat sheet — 80+ one-liners for text processing, with real examples and common pitfalls. Open
  3. 3 Vim Cheatsheet Vim cheat sheet — 100+ commands covering modes, motions, edits, search, registers, splits, with mnemonics. Open

FAQ

Tool combos

Folks in your role tend to reach for these alongside this tool.

Made by Toolora · 100% client-side · Updated 2026-05-29