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[[-f"/opt/app/config.yml"]];thenecho"Config found"elif[[-f"/etc/app/config.yml"]];thenecho"Using system config"elseecho"No config found, using defaults"exit1fi
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
While Loop
Syntax
while[[ condition ]];docommandsdone
Example
RETRIES=0while[[$RETRIES-lt5]];doifcurl-sf http://localhost:8080/health;thenecho"Service is healthy"breakfiecho"Retry ${RETRIES}..."sleep2((RETRIES++))done# Read file line by line:while IFS=read-r line;doecho"Processing: ${line}"done< servers.txt
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
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 $().
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.
TMPDIR=$(mktemp-d)trap'rm -rf "$TMPDIR"; echo "Cleaned up temp files"' EXIT
# Handle Ctrl+C gracefully:trap'echo "Interrupted!"; exit 130' INT
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.
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.