Bash Parameter Expansion Cheatsheet
Introduction¶
Bash's ${...} parameter expansion does string manipulation without spawning
sed, cut, awk, basename, or dirname. It's faster (no fork) and, once
learned, more readable. This is the practical subset.
Defaults and presence¶
${var:-default} # use "default" if var is unset or empty; var unchanged
${var:=default} # same, but also ASSIGN default to var
${var:+alt} # use "alt" if var IS set and non-empty, else nothing
${var:?message} # print "bash: var: message" to stderr and exit if unset/empty
port="${PORT:-8080}" # config with fallback
: "${CONFIG_DIR:=/etc/myapp}" # ensure it's set for the rest of the script
flags="${VERBOSE:+--verbose}" # add a flag only when VERBOSE is set
input="${1:?usage: deploy.sh <environment>}"
Drop the colon (${var-default}) to trigger only on unset (an empty value
counts as set).
Length¶
${#var} # length of the string
${#array[@]} # number of elements
[ "${#password}" -ge 12 ] || die "password too short"
Substrings¶
${var:offset} # from offset to end
${var:offset:length} # length chars from offset
${var: -4} # last 4 chars (note the space — ${var:-4} is the default form!)
${var: -4:2}
year="${date_str:0:4}"
last_octet="${ip##*.}" # see pattern removal below — better for this
Pattern removal (the basename/dirname/extension trio)¶
${var#pattern} # remove SHORTEST match from the FRONT
${var##pattern} # remove LONGEST match from the front
${var%pattern} # remove SHORTEST match from the END
${var%%pattern} # remove LONGEST match from the end
path="/var/log/nginx/access.log.1"
echo "${path##*/}" # access.log.1 (basename)
echo "${path%/*}" # /var/log/nginx (dirname)
echo "${path%.*}" # /var/log/nginx/access.log (strip last extension)
echo "${path##*.}" # 1 (last extension only)
file="archive.tar.gz"
echo "${file%%.*}" # archive (strip ALL extensions)
echo "${file#*.}" # tar.gz
Mnemonic: # is left of $ on the keyboard (front), % is right (end).
Search and replace¶
${var/pattern/replacement} # first match
${var//pattern/replacement} # all matches
${var/#pattern/replacement} # only if it matches at the START (anchored)
${var/%pattern/replacement} # only if it matches at the END
${var//pattern/} # delete all matches
csv="a,b,c,d"
echo "${csv//,/ }" # a b c d
branch="feature/JIRA-123-thing"
echo "${branch//\//-}" # feature-JIRA-123-thing (escape the / in the pattern)
echo "${filename// /_}" # spaces to underscores
Case conversion (bash 4+)¶
${var^} ${var^^} # first char / all to UPPER
${var,} ${var,,} # first char / all to lower
${var~~} # swap case
env="${ENVIRONMENT,,}" # normalise "PROD" / "Prod" -> "prod"
header="${key^^}"; header="${header//-/_}" # http-header -> HTTP_HEADER
Indirection and defaults for arrays¶
name="PATH"
echo "${!name}" # value of $PATH (indirect reference)
${!prefix*} # names of all variables starting with "prefix"
${array[@]:2:3} # 3 elements starting at index 2
${array[@]/%/.bak} # append .bak to every element
# Loop over all vars named MYAPP_*
for v in "${!MYAPP_@}"; do
printf '%s=%s\n' "$v" "${!v}"
done
Putting it together¶
# Rotate: access.log -> access.log.1, keeping the extension
for f in *.log; do
[ -e "$f" ] || continue
base="${f%.log}"
mv -- "$f" "${base}.$(date +%F).log"
done
# Normalise a URL to a safe filename
url="https://example.com/path/to/page?x=1"
name="${url#*://}" # strip scheme
name="${name//[^A-Za-z0-9]/_}" # non-alnum -> _
echo "$name" # example_com_path_to_page_x_1
Verification and troubleshooting¶
set -u # make ${var} on an unset var an error (catches typos)
bash -x script.sh # trace expansions
${var: -4}returns the wrong thing — you wrote${var:-4}(default value "4"). The substring-from-end form needs the space.- Replacement not happening — the pattern is glob, not regex.
.is literal,*matches anything.${s/./_}replaces the first literal dot. bad substitutionerror — running undersh/dash, not bash.^^,,,,${!x}are bash-only. Add#!/usr/bin/env bashand run it with bash./in the pattern breaks the expansion — escape it:${path//\//-}.- Case conversion does nothing — bash 3 (old macOS). Needs bash 4+.
Related tools and reading¶
- On-site: Regex Tester, Linux commands reference.
- Related posts: Writing a Bash script that won't bite you, journalctl recipes.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Linux & Sysadmin
resolv.conf and systemd-resolved
On modern distros /etc/resolv.conf points at a stub resolver managed by systemd-resolved. Editing it directly usually does nothing — here's the real model.
October 24, 2026Tuning Open File Limits on Linux
Four different limits control how many files a process can open. 'Too many open files' means you're hitting one specific one — here's how to find and raise it.
October 24, 2026ss and conntrack for Connection Debugging
ss shows you every socket and its send/receive queues; conntrack shows what the firewall is tracking. Together they explain most 'connection refused' and 'no buffer space' problems.
October 24, 2026