A quick-reference cheatsheet for Bash scripting and the
command line. Bash is both an interactive shell and a scripting
language available on virtually every Unix-like system. Scripts
start with #!/usr/bin/env bash and should be
made executable with chmod +x script.sh.
Variables are untyped strings by default. No spaces around
= when assigning. Always double-quote
variable expansions ("$var") to prevent word
splitting and glob expansion on values that contain spaces or
special characters.
name="Alice"
echo "$name" # always quote expansions
echo "${name}_suffix" # braces disambiguate the variable name
Default value:${var:-default} — use default if var is unset or empty${var:=default} — assign default if var is unset or empty
(without the colon: only triggers when var is unset, not empty)
readonly & export:readonly PI=3.14export PATH="$HOME/bin:$PATH"
(export makes the variable available to child processes)
Special variables:$0 script name · $1..$9 positional args$@ all args (array) · $# arg count$? last exit code · $$ current PID
Arithmetic:count=$((count + 1))((i++))
($((...)) is POSIX; use it instead of let or expr)
Bash provides built-in string manipulation via parameter
expansion — no need to invoke external tools like
sed or awk for simple
operations. Command substitution captures the output of a command
into a variable.
Length:${#var}
Substring:${var:offset:length}
(e.g. ${str:2:4} — starts at index 2, takes 4 chars)
Replace:${var/find/replace} — first match${var//find/replace} — all matches
Strip prefix / suffix:${var#prefix} / ${var##longest_prefix}${var%suffix} / ${var%%longest_suffix}
(# strips shortest match; ## strips longest)
Upper / lower case (Bash 4+):${var^^} uppercase · ${var,,} lowercase
files=$(ls -1 *.txt)
today=$(date +%Y-%m-%d)
echo "Backed up on $today"
(prefer $(...) over backticks — nestable and easier to read)
Bash conditions test exit codes — 0 means true, non-zero means
false. Use [[ ... ]] (Bash built-in) instead of
[ ... ] (POSIX test) — it handles empty strings and
spaces safely without quoting tricks, and supports
&&, ||, and regex matching.
if [[ "$1" == "start" ]]; then
echo "starting"
elif [[ "$1" == "stop" ]]; then
echo "stopping"
else
echo "unknown command"
fi
Common test operators:-z "$str" empty string · -n "$str" non-empty-f file is file · -d dir is directory · -e path exists-r / -w / -x readable / writable / executable
for file in *.txt; do
echo "Processing $file"
done
for i in {1..5}; do echo $i; done
for ((i=0; i<10; i++)); do echo $i; done
while IFS= read -r line; do
echo "$line"
done < input.txt
(IFS= preserves leading/trailing whitespace; -r prevents backslash interpretation)
case "$env" in
prod) echo "production" ;;
staging) echo "staging" ;;
dev|*) echo "development" ;;
esac
(| separates multiple patterns per case; * is a catch-all)
Functions in Bash are just named command groups. Arguments are
accessed as $1, $2, etc. —
there is no parameter list. Use local to scope
variables to the function; without it, all variables are global
by default.
greet() {
local name="${1:-World}"
echo "Hello, $name!"
}
greet "Alice" # Hello, Alice!
greet # Hello, World!
is_even() {
(( $1 % 2 == 0 )) # exit code 0 = true
}
if is_even 4; then echo "even"; fi
# capture output instead
get_user() { echo "alice"; }
user=$(get_user)
(functions return exit codes 0–255, not values; capture stdout for strings)
Bash supports indexed and associative arrays.
Always quote array expansions with "${arr[@]}"
to preserve elements that contain spaces.
"${arr[*]}" joins all elements into one string.
fruits=("apple" "banana" "cherry")
echo "${fruits[0]}" # apple
echo "${fruits[@]}" # all elements
echo "${#fruits[@]}" # length: 3
fruits+=("date") # append
for f in "${fruits[@]}"; do echo "$f"; done
declare -A colors
colors[red]="#ff0000"
colors[green]="#00ff00"
echo "${colors[red]}"
for key in "${!colors[@]}"; do
echo "$key = ${colors[$key]}"
done
Every process has three standard file descriptors: stdin (0), stdout (1), and stderr (2). Redirection operators route these streams to files or other commands. Pipes chain commands so stdout of one becomes stdin of the next.
Redirect stdout / stderr:cmd > out.txt — overwritecmd >> out.txt — appendcmd 2> err.txt — stderr onlycmd > out.txt 2>&1 — stdout + stderr together
Discard output:cmd > /dev/null 2>&1
(send both streams to the void)
ps aux | grep nginx | awk '{print $2}'
cat access.log | sort | uniq -c | sort -rn | head -20
cat <<EOF
line one
line two
EOF
# indented here-doc (Bash 4+, strips leading tabs)
cat <<-EOF
line one
line two
EOF
read -rp "Enter name: " name
read -rs -p "Password: " pass # -s = silent, no echo
(-r = raw, no backslash processing; -p = inline prompt)
Bash can run commands in the background, wait for them, and
check their exit codes. The trap built-in lets
you handle signals and run cleanup code when a script exits.
long_task &
pid=$!
wait $pid # wait for specific PID
($! holds the PID of the last background command)
cmd && echo "ok" || echo "failed"
cmd; echo "exit: $?"
tmpfile=$(mktemp)
trap "rm -f $tmpfile" EXIT
# also handle Ctrl-C
trap "echo 'interrupted'; exit 1" INT TERM
(EXIT fires on any exit — normal or signal; safest place for cleanup)
A few idioms that make scripts more robust and maintainable.
set -euo pipefail should be at the top of almost
every script — it prevents silent failures that are hard to
debug later.
#!/usr/bin/env bash
set -euo pipefail
# -e exit on error
# -u error on unset variables
# -o pipefail pipe fails if any command fails
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# use $DIR to reference files relative to the script
if ! command -v docker &> /dev/null; then
echo "docker is required but not installed" >&2
exit 1
fi
while getopts "vf:" opt; do
case $opt in
v) verbose=true ;;
f) file="$OPTARG" ;;
*) echo "Usage: $0 [-v] [-f file]" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1)) # remaining positional args
if [[ "$EUID" -ne 0 ]]; then
echo "Run as root" >&2
exit 1
fi