/lessons/2026-03-27
Shell Parameter Expansion: Beyond ${var}
Bash parameter expansion can replace most uses of sed, awk, and cut for string manipulation. Learn the syntax once, use it everywhere.
Every time you write $(echo "$var" | sed 's/foo/bar/') in a shell script, there's probably a parameter expansion that does it faster and without spawning a subprocess.
Default Values
${var:-default} # Use default if var is unset or empty
${var:=default} # Assign default if var is unset or empty
${var:+alternate} # Use alternate if var IS set and non-empty
${var:?error msg} # Exit with error if var is unset or empty
The colon matters. Without it (${var-default}), only checks if unset, not empty.
String Manipulation
${var#pattern} # Remove shortest match from beginning
${var##pattern} # Remove longest match from beginning
${var%pattern} # Remove shortest match from end
${var%%pattern} # Remove longest match from end
Real Examples
filepath="/home/user/documents/report.tar.gz"
${filepath##*/} # report.tar.gz (basename)
${filepath%/*} # /home/user/documents (dirname)
filename="${filepath##*/}"
${filename%%.*} # report (name without any extension)
${filename%.*} # report.tar (name without last extension)
${filename##*.} # gz (last extension only)
Substitution
${var/pattern/replacement} # Replace first match
${var//pattern/replacement} # Replace all matches
${var/#pattern/replacement} # Replace if at beginning
${var/%pattern/replacement} # Replace if at end
Length and Slicing
${#var} # String length
${var:offset} # Substring from offset
${var:offset:length} # Substring with length
Case Conversion (Bash 4+)
${var^^} # UPPERCASE
${var,,} # lowercase
${var^} # Capitalize first letter
Pro Tip
Combine expansions for powerful one-liners. Convert a filename from snake_case to kebab-case without any external tools:
file="my_cool_script.sh"
echo "${file//_/-}" # my-cool-script.sh
Or strip a common prefix from all files in a directory:
prefix="project_v2_"
for f in ${prefix}*; do
mv "$f" "${f#$prefix}"
done
Example
A practical script that processes log file paths — extracting date, service name, and rotating based on age — using nothing but parameter expansion:
#!/bin/bash
for logfile in /var/log/services/*.2026-*.log; do
base="${logfile##*/}" # api-gateway.2026-03-15.log
service="${base%%.*}" # api-gateway
date_part="${base#*.}" # 2026-03-15.log
date_part="${date_part%.*}" # 2026-03-15
echo "Service: $service, Date: $date_part"
done
No awk. No sed. No cut. Just the shell doing what it was designed to do.