Defaults, Errors, and Alternate Values
Bash's ${VAR:-default} expands to default only if VAR is unset or empty, without modifying VAR itself, while ${VAR:=default} does the same but also assigns default back into VAR; ${VAR:?message} exits the script with message printed to stderr if VAR is unset or empty, which is invaluable for validating required arguments early; and ${VAR:+alt} expands to alt only if VAR IS set and non-empty, useful for conditionally appending flags.
Cricket analogy: A team's reserve batsman only walks in (${VAR:-default}) if the regular opener is unavailable, without replacing them permanently on the team sheet, whereas officially transferring the reserve onto the playing eleven mirrors ${VAR:=default} persisting the assignment.
#!/usr/bin/env bash
set -euo pipefail
# :- gives a default without modifying the variable
port="${PORT:-8080}"
# := assigns the default back into the variable
: "${LOG_LEVEL:=info}"
echo "Log level is now permanently set to: $LOG_LEVEL"
# :? exits with an error message if unset/empty
db_host="${DB_HOST:?Error: DB_HOST must be set}"
# :+ expands only if the variable IS set and non-empty
verbose_flag="${VERBOSE:+--verbose}"
curl $verbose_flag "https://$db_host:$port/health"
Substring Removal and Pattern Substitution
The pattern-removal operators ${VAR#pattern} and ${VAR##pattern} strip the shortest and longest matching prefix respectively, while ${VAR%pattern} and ${VAR%%pattern} strip the shortest and longest matching suffix — extremely common for stripping file extensions or directory paths without spawning basename/dirname subprocesses. Pattern substitution with ${VAR/pattern/replacement} replaces the first match and ${VAR//pattern/replacement} replaces all matches, giving sed-like find-and-replace power natively in the shell.
Cricket analogy: Trimming a bowler's long run-up down to a shorter approach for a T20 over is like ${VAR%pattern} trimming a trailing suffix, while completely removing the run-up for a rapid bowl-out mirrors ${VAR%%pattern} stripping the longest possible suffix match.
Mnemonic: # removes from the front (think of # as pointing left toward the start), % removes from the back (think of % as trailing off toward the end). Doubling the operator (## or %%) makes the match greedy (longest possible), while a single # or % is non-greedy (shortest possible match).
path="/var/log/app/access.log.gz"
echo "${path##*/}" # access.log.gz (strip longest prefix up to last /)
echo "${path%.gz}" # /var/log/app/access.log (strip .gz suffix)
echo "${path%%.*}" # /var/log/app/access (strip longest suffix from first .)
filename="IMG_2026_beach_vacation.jpg"
echo "${filename/beach/mountain}" # replace first match
echo "${filename//_/-}" # replace ALL underscores with hyphens
Length, Case Conversion, and Indirection
${#VAR} returns the character length of a string (or element count for an array), which is handy for input validation without calling wc or expr. Bash 4+ added case-conversion operators: ${VAR^} and ${VAR^^} uppercase the first character or the whole string respectively, while ${VAR,} and ${VAR,,} lowercase the first character or whole string. Indirect expansion with ${!VAR} treats VAR's value as the name of another variable and expands that variable instead, enabling a form of dynamic variable referencing without eval.
Cricket analogy: ${#VAR} checking a string's length is like the scoreboard instantly reporting the number of runs scored without a manual recount; ${VAR^^} uppercasing a whole team abbreviation like 'ind' to 'IND' mirrors the broadcast graphics standardizing team codes for the scoreboard.
${!VAR} indirect expansion and eval-based dynamic variable access are powerful but can make scripts harder to read and reason about, and if VAR's value ever comes from untrusted input, indirection combined with eval can become a code-injection vector. Prefer associative arrays (covered later in this course) over indirect variable naming whenever you need a genuine key-to-value mapping — it is safer and clearer.
- ${VAR:-default} and ${VAR:=default} supply fallback values, but only := actually persists the default into VAR.
- ${VAR:?message} enforces required variables by exiting with an error if VAR is unset or empty.
- ${VAR:+alt} expands only when VAR is set and non-empty, useful for conditional flags.
- # and ## strip prefixes (shortest/longest match); % and %% strip suffixes (shortest/longest match).
- ${VAR/pat/repl} replaces the first match; ${VAR//pat/repl} replaces all matches, like sed's g flag.
- ${#VAR} returns string length; ${VAR^^}/${VAR,,} convert case without external tools like tr.
- ${!VAR} performs indirect expansion, but should be used sparingly in favor of associative arrays for clarity and safety.
Practice what you learned
1. What is the key difference between ${VAR:-default} and ${VAR:=default}?
2. Given path="/a/b/c.tar.gz", what does ${path%%.*} produce?
3. Which expansion enforces that a variable must be set, exiting the script with an error message otherwise?
4. What does ${VAR^^} do?
5. What security caution applies to ${!VAR} indirect expansion?
Was this page helpful?
You May Also Like
Review of Bash Basics
A refresher on core Bash concepts—shell startup, variables, quoting, and control flow—that underpin the advanced scripting techniques covered later in this course.
Arrays in Depth
A thorough look at Bash indexed arrays: declaration, expansion pitfalls, slicing, appending, iteration, and the differences between @ and * expansions that trip up even experienced scripters.
Associative Arrays
Bash 4+ associative arrays bring key-value maps to shell scripting — learn declaration, iteration order, key existence checks, and real-world patterns like counters and lookup tables.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics