name=value
$name or ${name}
"double quotes" vs 'single quotes'
example
APP_ENV="production"
PORT=8080echo"Server running on port ${PORT} in ${APP_ENV}"
FILES=$(ls *.conf)
READONLY_VAR="locked"
readonly READONLY_VAR
output
Server running on port 8080 in production
Note No spaces around the = sign. Double quotes expand variables; single quotes are literal. Always double-quote variable expansions ("$var") to prevent word splitting and glob expansion. Use $() for command substitution instead of backticks.
if [[ condition ]]; then
commands
elif [[ condition ]]; then
commands
else
commands
fi
example
if [[ -f "/opt/app/config.yml" ]]; thenecho"Config found"elif [[ -f "/etc/app/config.yml" ]]; thenecho"Using system config"elseecho"No config found, using defaults"
exit 1fi
Note Use [[ ]] (double bracket) over [ ] for safer string comparisons and pattern matching. Common file tests: -f (file exists), -d (directory exists), -r (readable), -z (string is empty), -n (string is not empty). Use && and || inside [[ ]].
for host in web01 web02 web03; doecho"Deploying to ${host}..."
scp app.tar.gz deploy@"${host}":/opt/releases/
done# C-style:for ((i=1; i<=5; i++)); doecho"Attempt ${i}"done
output
Deploying to web01...
Deploying to web02...
Deploying to web03...
Note Never parse ls output in a for loop. Use globs instead: for f in *.log; do ... done. For iterating over lines in a file: while IFS= read -r line; do ... done < file.txt.
bash for loopiterate listloop over filesc-style loopfor each
Note break exits the loop. continue skips to the next iteration. The 'read file line by line' pattern is the correct way to process files; for loops split on whitespace, which causes bugs with filenames containing spaces.
bash whileloop untilretry loopread file line by linewhile condition
Case Statement
syntax
case $variable in
pattern1) commands ;;
pattern2) commands ;;
*) default ;;
esac
Note Each branch ends with ;;. Patterns support globbing and | for alternatives. case is cleaner than long if/elif chains when matching a single variable against many values. *) is the default/catch-all branch.
Note Use local to scope variables to the function (without local, variables are global). Arguments are accessed via $1, $2, etc. $@ is all arguments. return sets the exit code (0-255); to return strings, echo them and capture with $().
bash functiondefine functionlocal variablefunction argumentsreturn value
Exit Codes & Error Handling
syntax
$?
set -e
set -o pipefail
command || handle_error
example
#!/usr/bin/env bash
set -euo pipefail
grep -q 'ready' status.txt || { echo'Not ready'; exit 1; }
if ! deploy_app; thenecho"Deploy failed with code $?"
rollback
exit 1fi
Note $? holds the exit code of the last command (0 = success, non-zero = failure). set -e aborts the script on any non-zero exit. set -u treats unset variables as errors. set -o pipefail catches failures in piped commands. Always use all three in production scripts.
Note EXIT runs on any script exit (normal or error). INT catches Ctrl+C. TERM catches kill signals. trap is essential for cleaning up temp files, releasing locks, or restoring state. Multiple traps on the same signal replace the previous one.
cleanup on exittrap signalhandle ctrl ctemp file cleanupsignal handler
Note Indices start at 0. Always quote "${arr[@]}" to preserve elements with spaces. ${#arr[@]} gives the length. += appends. unset 'arr[1]' removes an element (but does not reindex). Bash arrays are not available in sh/POSIX shell.
bash arrayarray looplist of valuesarray lengthadd to array
Note ## removes longest prefix match, # removes shortest. %% removes longest suffix, % removes shortest. These are pure Bash operations (no subprocess), so they are much faster than calling sed or basename in a loop.
string manipulationsubstringbasename in bashstring replacestring lengthparameter expansion