Parameter Expansion
Master all the ${...} forms — default values, error on unset, string length, prefix/suffix stripping, substitution, and case conversion.
- Provide default and fallback values with :- and :=
- Abort with an error message using :?
- Measure string length with ${#var}
- Strip prefixes and suffixes with ${var#}, ${var##}, ${var%}, ${var%%}
- Replace substrings with ${var/old/new} and ${var//old/new}
- Convert case with ${var^^} and ${var,,}
Parameter expansion — the ${...} forms — is Bash's built-in string manipulation layer. Reaching for awk or sed to strip a file extension or substitute a prefix is common in beginner scripts; once you know parameter expansion, most of those one-liners disappear and the remaining script is faster (no subprocess) and more readable.
Default and fallback values
These operators handle unset or empty variables without an explicit if:
# :- use a default if unset or empty
echo "${name:-Alice}" # prints "Alice" if $name is unset or ""
echo "${count:-0}" # safe numeric default
# := assign AND use a default (modifies the variable)
: "${tmpdir:=/tmp}" # set $tmpdir to /tmp if not already set
echo "$tmpdir" # /tmp
# :+ substitute a different value if the variable IS set (non-empty)
echo "${debug:+--verbose}" # prints --verbose only if $debug is set
# :? exit with an error message if unset or empty
: "${REQUIRED_VAR:?REQUIRED_VAR must be set}"The :? form is the idiomatic way to make a required environment variable explicit — it produces a clear error and exits the script immediately.
Without the colon, these operators treat only unset variables specially, not empty ones. ${var-default} gives the default only if var is unset; ${var:-default} gives it if var is unset or empty. For scripts, the colon versions are almost always what you want.
String length
name="Alice"
echo "${#name}" # 5
path="/usr/local/bin"
echo "${#path}" # 14
arr=(a b c d)
echo "${#arr[@]}" # 4 (array element count, not string length)Prefix and suffix stripping
# strips from the left (prefix); % strips from the right (suffix). Doubled forms (##, %%) are greedy (strip as much as possible):
path="/home/alice/docs/report.pdf"
# Strip a prefix
echo "${path#/home/}" # alice/docs/report.pdf (shortest match)
echo "${path##*/}" # report.pdf (longest match — basename)
# Strip a suffix
echo "${path%.pdf}" # /home/alice/docs/report (shortest match)
echo "${path%%/*}" # "" (longest match strips everything after first /)This covers the two most common use cases: extracting a filename from a path (${path##*/}) and stripping a file extension (${path%.*}):
file="archive.tar.gz"
echo "${file%.*}" # archive.tar (strip last extension)
echo "${file%%.*}" # archive (strip all extensions)Substring substitution
path="/usr/local/bin:/usr/bin"
echo "${path/bin/lib}" # /usr/local/lib:/usr/bin (first match only)
echo "${path//bin/lib}" # /usr/local/lib:/usr/lib (all matches, // is global)
# Delete by replacing with nothing
echo "${path//:/}" # /usr/local/bin/usr/binCase conversion
name="alice"
echo "${name^}" # Alice (first character uppercase)
echo "${name^^}" # ALICE (all uppercase)
title="HELLO WORLD"
echo "${title,}" # hELLO WORLD (first character lowercase)
echo "${title,,}" # hello world (all lowercase)Case conversion requires Bash 4+. Like associative arrays, ^^ and ,, are unavailable on macOS's default Bash 3.2. For portable scripts, use tr '[:lower:]' '[:upper:]' instead.
Combining expansions
Parameter expansions compose naturally:
# Extract extension and convert to lowercase
file="Report.TXT"
ext="${file##*.}"
echo "${ext,,}" # txt
# Build a backup filename
orig="data.csv"
backup="${orig%.*}.$(date +%Y%m%d).${orig##*.}"
echo "$backup" # data.20240115.csvCheck your understanding
- 1.path="/var/log/nginx/access.log". Which expansion gives you "access.log"?
- 2.LOGFILE="" and you run echo "${LOGFILE:-/var/log/app.log}". What is printed?
- 3.${var/old/new} replaces all occurrences of "old" in $var.
Do it yourself
# Basename and extension extraction
path="/home/user/projects/app.tar.gz"
echo "basename: ${path##*/}"
echo "no ext: ${path%.*}"
echo "all exts: ${path%%.*}"
# Defaults
: "${EDITOR:=nano}"
echo "Editor: $EDITOR"
# Case conversion (Bash 4+)
tag="PRODUCTION_SERVER"
echo "${tag,,}" # production_server
# Substitute in a path
old_path="/home/olduser/data"
new_path="${old_path/olduser/newuser}"
echo "$new_path"Where to go next
You now have the full set of ${...} tools for string manipulation without subprocesses. The next lesson — Heredocs and herestrings — covers <<EOF, <<<, and process substitution, which let you pass multi-line text and command output to programs in flexible, readable ways.
Arrays
Use Bash indexed arrays and associative arrays — creating, expanding, slicing, looping, and understanding when arrays beat space-separated strings.
Heredocs and Herestrings
Use <<EOF heredocs, <<-EOF for indented scripts, <<< herestrings, and process substitution with <(cmd) and >(cmd) to pass data without temp files.