Bash Parameter Expansion Reference

All Bash parameter expansion forms with substitution, trimming and case modifiers.

Interactive, searchable reference for Bash ${} parameter expansion: default and alternate values, substring extraction, prefix/suffix removal, pattern replacement, case modification, length and indirection — each with a working example. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What is the difference between ${x:-word} and ${x-word}?

The colon form treats an unset variable and a set-but-empty variable the same way, substituting word for both. Without the colon, only a fully unset variable triggers the substitution; an empty string is kept as-is.

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.