Bash parameter expansion
Parameter expansion is the ${...} syntax that lets Bash transform a variable’s
value as it is substituted into a command — without calling external tools like
sed, cut or tr. It covers default values, substring slicing, pattern-based
trimming, search-and-replace, case changes, length and indirection. This page is
a searchable, offline reference: filter by keyword or group, read a working
example for each form, and click any pattern to copy it.
How it works
Every form is interpreted by the shell itself before the command runs. The core families are:
- Default / alternate —
${var:-w},${var:=w},${var:+w},${var:?w}decide what to substitute when a variable is unset or null. A leading colon also treats an empty value as “null”; without it, only fully unset variables qualify. - Substring —
${var:offset:length}slices by character index (0-based); negative offsets count from the end. - Removal —
#/##strip a prefix and%/%%strip a suffix using glob patterns; the doubled form is greedy (longest match). - Replacement —
${var/pat/str}(first) and${var//pat/str}(all), with/#and/%anchoring to the start or end. - Case —
^/^^uppercase and,/,,lowercase (Bash 4+). - Length / indirection —
${#var}returns length;${!var}dereferences.
Worked examples: file path manipulation
Combine forms to build robust scripts without calling external tools:
path=/var/log/app.log
base=${path##*/} # app.log (strip longest leading dir)
name=${base%.*} # app (strip trailing extension)
ext=${path##*.} # log (just the extension)
dir=${path%/*} # /var/log (directory portion)
This replaces dirname, basename, and a sed call with four pure-shell expansions that run in every POSIX-compatible shell.
Worked example: config defaults
Use the assign-default form to set values once and reuse them safely:
: "${PORT:=8080}"
: "${HOST:=localhost}"
: "${LOG_LEVEL:=info}"
echo "Starting server on ${HOST}:${PORT} (log: ${LOG_LEVEL})"
The : command (which is a no-op) runs the assignment expansion for side effects. If PORT is already set, the := form leaves it unchanged and the script runs with the caller’s value.
Tricky behaviours to know
Colon vs no-colon matters. ${var:-default} substitutes when var is unset or empty. ${var-default} substitutes only when var is unset; an empty var passes through unchanged. Most scripts want the colon form.
Patterns are globs, not regex. In ${var#pat}, pat is a shell glob — * matches any string, ? matches one character, [abc] matches a class. Regular expressions are not supported; use [[ $var =~ regex ]] for that.
Greedy vs non-greedy. ${var#pat} is non-greedy (shortest match from the left). ${var##pat} is greedy (longest match). Same for % (non-greedy from right) and %% (greedy from right):
file=2024.01.15.log
echo "${file#*.}" # 01.15.log (strip first dot-prefix)
echo "${file##*.}" # log (strip all but extension)
Negative substring offsets require a space. ${var: -3} is a negative offset (last 3 chars); ${var:-3} is the default-value form. The space is mandatory:
str=abcdefg
echo "${str: -3}" # efg (last 3 characters)
echo "${str:-3}" # abcdefg (unchanged — str is not empty)
Note that these operate on the string value of the variable, not on the filesystem — the patterns in #, % and / never touch files directly.