๐งฉ Advanced Shell Expansions
๐ง Overview
Shell expansions are the heart of the shell execution model. Before any command runs, the shell rewrites the input through a deterministic but complex sequence of transformations: parameter expansion, command substitution, arithmetic expansion, pathname expansion, word splitting, and quote removal.
Most subtle bugs, security issues, and unexpected behaviors originate here.
๐ Who this is for
- Engineers writing productionโgrade shell scripts.
- DevOps/SRE dealing with CI/CD, containers, and automation.
- Anyone debugging quoting, variable behavior, or unexpected command output.
- People who want to understand the shell as a transformation engine.
๐งฉ Internals / Mechanics
๐งฉ The expansion order (POSIX)
Shell expansions occur in this strict order:
- Brace expansion (Bash only)
- Tilde expansion
- Parameter expansion (
$VAR, ${VAR}, ${VAR:-default})
- Command substitution (
$(...), `...`)
- Arithmetic expansion (
$(( ... )))
- Word splitting
- Pathname expansion (globbing:
*, ?, [...])
- Quote removal
Understanding this order is essential for predicting behavior.
๐งฉ Parameter expansion
Examples:
| ${VAR:-default} # default if unset or empty
${VAR:=default} # assign default if unset or empty
${VAR:+alt} # alt if VAR is set
${VAR:?error} # error if unset or empty
|
๐งฉ Command substitution
Runs in a subshell:
Always strips trailing newlines.
๐งฉ Arithmetic expansion
Uses Cโstyle integer arithmetic.
๐งฉ Word splitting
Controlled by $IFS.
Happens after parameter/command/arithmetic expansion.
๐งฉ Pathname expansion (globbing)
Expands to matching filenames unless nullglob or failglob is set.
๐ง Techniques
๐ง Always quote variables unless you want splitting
๐ง Use arrays to avoid splitting issues (Bash)
๐ง Use printf instead of echo
๐ง Use ${var%pattern} and ${var#pattern} for trimming
| file="backup.tar.gz"
echo "${file%.gz}" # backup.tar
|
โ ๏ธ Pitfalls
โ ๏ธ Unquoted variables causing splitting
If $DIR is empty โ expands to rm -rf /*.
โ ๏ธ Command substitution strips newlines
| lines=$(printf "a\nb\n")
printf "%s" "$lines" # prints "ab"
|
โ ๏ธ Globs expanding unexpectedly
If no files exist, behavior depends on shell options.
โ ๏ธ Brace expansion confusion
Bash expands this, POSIX shells do not.
๐จ RealโWorld Failures
๐จ Failure: CI job deletes root directory
If $WORKSPACE is empty โ expands to rm -rf /*.
๐จ Failure: Command substitution breaks JSON
| json=$(cat file.json)
echo "$json" # newlines stripped โ invalid JSON
|
๐จ Failure: Word splitting breaks arguments
If $pattern contains spaces โ multiple arguments.
๐ ๏ธ Patterns
๐ ๏ธ Pattern: Quote everything by default
๐ ๏ธ Pattern: Use arrays for lists
๐ ๏ธ Pattern: Use printf for safe output
๐ ๏ธ Pattern: Validate variables before expansion
| : "${WORKSPACE:?must be set}"
|
โ AntiโPatterns
โ Antiโpattern: Using echo for structured data
echo is not reliable for JSON, XML, or binary data.
โ Antiโpattern: Relying on implicit splitting
โ Antiโpattern: Unquoted globs
๐ Debugging
๐ Use set -x to trace expansions
Shows expanded commands before execution.
๐ Use printf '%q' (Bash) to inspect quoting
๐ Use declare -p to inspect variables
โ๏ธ Avoid unnecessary command substitutions
| var=$(cat file) # slow
var=$(<file) # fast
|
โ๏ธ Avoid globbing in tight loops
Use arrays or find.
๐งต Process Control
Expansions themselves do not spawn processes,
but command substitution does:
| result=$(cmd) # always forks
|
๐ณ Containers
๐ณ Expansion issues in entrypoints
Environment variables may be empty or unset.
Always validate before expansion.
๐ฐ๏ธ CI/CD
๐ฐ๏ธ Fail fast on unset variables
๐ง Summary
Shell expansions are a deterministic but complex transformation pipeline.
Mastering them prevents:
- data corruption
- security issues
- accidental deletions
- broken CI pipelines
Expansions are where most shell bugs originate โ understanding them is essential for writing safe, predictable scripts.