Parameter Expansion
String manipulation without external tools — path extraction, default values, substitution, case conversion. Pure bash, no fork overhead.
Parameter Expansion
String manipulation without calling external tools — pure bash, no fork.
Path Manipulation
Extract basename and dirname without external commands
var="docs/modules/ROOT/pages/codex/bash/safe-workflows.adoc"
echo "${var##*/}" # safe-workflows.adoc (basename — strip longest prefix up to /)
echo "${var%/*}" # docs/modules/ROOT/pages/codex/bash (dirname — strip shortest suffix from /)
echo "${var##*.}" # adoc (extension — strip longest prefix up to .)
echo "${var%.*}" # docs/modules/ROOT/pages/codex/bash/safe-workflows (strip extension)
Mnemonic: # strips from left, % strips from right
# Single # or % — shortest match (greedy from the boundary)
# Double ## or %% — longest match (greedy through the string)
file="backup.2026.04.10.tar.gz"
echo "${file#*.}" # 2026.04.10.tar.gz (shortest — first dot)
echo "${file##*.}" # gz (longest — last dot)
echo "${file%.*}" # backup.2026.04.10.tar (shortest from right)
echo "${file%%.*}" # backup (longest from right)
Defaults and Guards
Default value if unset
echo "${EDITOR:-vim}" # uses vim if EDITOR is unset or empty
echo "${EDITOR-vim}" # uses vim if EDITOR is unset (empty string is kept)
Assign default if unset
: "${TMPDIR:=/tmp}" # sets TMPDIR to /tmp if unset
# : is a no-op — prevents the expansion from being executed as a command
Error if unset
: "${API_TOKEN:?API_TOKEN must be set}"
# Exits with error message if API_TOKEN is unset or empty
Substitution
Replace first occurrence
path="codex/cli/grep.adoc"
echo "${path/cli/grep}" # codex/grep/grep.adoc
Replace all occurrences
dashed="port-smtp-submission"
echo "${dashed//-/_}" # port_smtp_submission
Case conversion (bash 4+)
name="evan"
echo "${name^}" # Evan (capitalize first)
echo "${name^^}" # EVAN (all upper)
LOUD="HELLO"
echo "${LOUD,}" # hELLO (lowercase first)
echo "${LOUD,,}" # hello (all lower)
Length and Substring
String length
str="hello"
echo "${#str}" # 5
Substring extraction
str="hello world"
echo "${str:6}" # world (from position 6)
echo "${str:0:5}" # hello (from 0, length 5)