🧩 Advanced Shell Expansions (Full POSIX + Bash Edition)
🧠 Overview
Shell expansions are the core transformation engine of every POSIX‑style shell. Before the shell executes anything, it rewrites the input through a deterministic pipeline of expansion phases. These phases interact in subtle, sometimes surprising ways — especially when mixing POSIX semantics with Bash‑specific features.
This module provides a complete, exhaustive, production‑grade reference for expansions in:
- POSIX shells (dash, ash, busybox, ksh)
- Bash (full feature set)
- Zsh (differences noted where relevant)
This is the definitive expansions module for advanced engineers, SREs, DevOps, and anyone writing automation that must be correct, safe, and predictable.
🎓 Who this is for
- Senior engineers writing production‑grade shell scripts
- DevOps/SRE building CI/CD pipelines, container entrypoints, automation
- Engineers debugging quoting, splitting, globbing, or substitution issues
- People who want to understand the shell as a deterministic compiler
- Anyone who has ever said “why the hell did the shell do THAT?”
🧩 Role in the Ecosystem
Expansions interact with:
- Shell Architecture
- Redirections & File Descriptors
- Subshells & Environment
- Advanced Pipelines
- Execve & Fork Internals
- Advanced Error Handling
If you misunderstand expansions, you misunderstand the shell.
1. Expansion Pipeline (POSIX + Bash Differences)
🧩 1.1 POSIX Expansion Order
POSIX defines the following strict order:
- Brace expansion (Bash-only; POSIX ignores)
- Tilde expansion
- Parameter expansion
- Command substitution
- Arithmetic expansion
- Word splitting
- Pathname expansion (globbing)
- Quote removal
This order is not negotiable. Every shell script bug involving quoting or splitting comes from misunderstanding this pipeline.
🧩 1.2 Bash Expansion Order
Bash adds:
- brace expansion
- arrays
- associative arrays
- extended globbing
- case modification
- indirect expansion
- name references
- process substitution
Bash expansion order:
- Brace expansion
- Tilde expansion
- Parameter expansion
- Command substitution
- Arithmetic expansion
- Word splitting
- Globbing
- Quote removal
Same as POSIX, but with more features inside each phase.
🧩 1.3 Zsh Differences (important!)
Zsh:
- performs globbing before splitting (opposite of POSIX/Bash)
- has recursive globbing (
**) - has glob qualifiers (
(.), (N), (D), (/)) - has extended globbing enabled by default
- has brace expansion always enabled
- has different rules for arrays and splitting
This module focuses on POSIX + Bash, but Zsh differences are noted where they matter.
🧩 1.4 Expansion Pipeline Visualized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
🧩 1.5 Expansion Pipeline Invariants
- Expansions happen before execution.
- Expansions happen left-to-right, but in phases.
- Quoting controls splitting and globbing, not parameter expansion.
- Command substitution always runs in a subshell.
- Word splitting is the most dangerous phase.
- Globbing happens after splitting.
- Quote removal happens last.
2. Parameter Expansion (POSIX + Bash)
Parameter expansion is the most powerful and most misused expansion phase.
🧩 2.1 Basic Forms
1 2 | |
{} are required when:
- concatenating:
${VAR}suffix - using operators:
${VAR:-default}
🧩 2.2 Defaulting Operators
1 2 | |
🧩 2.3 Assignment Operators
1 2 | |
🧩 2.4 Alternate Value Operators
1 2 | |
🧩 2.5 Error Operators
1 2 | |
If VAR is unset (or empty with :?), the shell prints the message and exits.
🧩 2.6 Length Operator
1 | |
Length in characters (Bash: bytes unless using Unicode mode).
🧩 2.7 Substring Extraction
1 2 | |
Negative offsets allowed in Bash:
1 | |
🧩 2.8 Pattern Removal
From the end:
1 2 | |
From the beginning:
1 2 | |
Example:
1 2 3 | |
🧩 2.9 Pattern Substitution
1 2 3 4 | |
🧩 2.10 Case Modification (Bash-only)
1 2 3 4 | |
🧩 2.11 Indirect Expansion (Bash-only)
1 2 | |
🧩 2.12 Name References (Bash-only)
1 2 | |
🧩 2.13 Expansion in Assignment Context
1 2 | |
Assignments do not undergo word splitting or globbing.
🧩 2.14 Expansion in Arithmetic Context
1 | |
Arithmetic context has its own expansion rules.
3. Command Substitution
Command substitution runs a command and replaces the substitution with its output.
🧩 3.1 POSIX Rules
1 2 | |
- runs in a subshell
- strips trailing newlines
- performs parameter expansion inside
🧩 3.2 Bash Rules
Bash adds:
- nested command substitution
- command substitution inside arrays
- command substitution inside arithmetic
🧩 3.3 Newline Stripping
This is one of the most dangerous behaviors:
1 2 3 | |
🧩 3.4 Nested Command Substitution
1 | |
🧩 3.5 Command Substitution in Pipelines
1 | |
Runs the entire pipeline in a subshell.
🧩 3.6 Command Substitution in Redirections
1 | |
🧩 3.7 Command Substitution in Arrays (Bash)
1 | |
Dangerous — splitting + globbing occur.
🧩 3.8 Performance Considerations
- each substitution = fork
- avoid in tight loops
- prefer
<fileover$(cat file)
4. Arithmetic Expansion
🧩 4.1 POSIX Arithmetic
1 | |
Integer-only.
🧩 4.2 Bash Arithmetic
Supports:
- base prefixes (
0x,08,2#1010) - variables without
$ - side effects (
i++,++i)
🧩 4.3 Integer Rules
- always integers
- overflow behavior depends on shell
🧩 4.4 Base Prefixes
1 2 | |
🧩 4.5 Side Effects
1 2 | |
🧩 4.6 Arithmetic in Loops
1 | |
🧩 4.7 Arithmetic in Arrays
1 | |
5. Word Splitting (POSIX + Bash)
Word splitting is one of the most dangerous and misunderstood phases of the expansion pipeline. It happens after parameter/command/arithmetic expansion, and before globbing.
🧩 5.1 POSIX Word Splitting Rules
Word splitting occurs when:
- the result of expansion is unquoted, and
- the expansion produced unquoted whitespace, and
$IFScontains characters that match that whitespace.
Default IFS:
1 | |
🧩 5.2 Bash Word Splitting Rules
Bash follows POSIX, but adds:
$'...'quoting (no splitting)- arrays (no splitting when expanding
"${arr[@]}") mapfile(no splitting)read -a(splitting into array)
🧩 5.3 IFS Semantics
IFS has three classes:
- Whitespace characters (space, tab, newline)
- Non-whitespace characters
- Unset IFS
Whitespace IFS:
- consecutive whitespace collapses into a single delimiter
- leading/trailing whitespace is ignored
Non-whitespace IFS:
- each character is a delimiter
- consecutive delimiters produce empty fields
Example:
1 2 | |
Splits on :.
🧩 5.4 Empty IFS
1 | |
→ disables splitting entirely.
🧩 5.5 IFS=$'\n\t'
This is a common safe mode:
1 | |
Removes splitting on spaces.
🧩 5.6 Word Splitting in Arrays (Bash)
1 | |
Dangerous — splitting + globbing occur.
Safe:
1 | |
🧩 5.7 Word Splitting in Command Substitution
1 | |
Splits on whitespace → never use ls for parsing.
🧩 5.8 Word Splitting in eval
1 | |
→ expansion → splitting → globbing → eval → execution Four layers of danger.
🧩 5.9 Word Splitting in for Loops
1 | |
Splits on IFS.
Safe:
1 | |
6. Pathname Expansion (Globbing)
Globbing happens after word splitting.
🧩 6.1 POSIX Globbing
Supported patterns:
*— any string?— any single character[...]— character class
🧩 6.2 Bash Globbing
Bash adds:
extglobglobstar(**)dotglobnullglobfailglobnocaseglob
🧩 6.3 nullglob, failglob, dotglob
nullglob
1 | |
If no match → expands to empty list.
failglob
1 | |
If no match → error.
dotglob
1 | |
Includes dotfiles.
🧩 6.4 extglob
Enable:
1 | |
Patterns:
@(pattern-list)— one of!(pattern-list)— anything except?(pattern-list)— zero or one*(pattern-list)— zero or more+(pattern-list)— one or more
Example:
1 | |
Deletes everything except .txt.
🧩 6.5 Glob Qualifiers (Zsh)
Examples:
1 2 3 | |
🧩 6.6 Globbing in Arrays
1 | |
🧩 6.7 Globbing in Redirections
1 | |
If multiple matches → error.
🧩 6.8 Globbing in Pipelines
1 | |
7. Brace Expansion (Bash-only)
Brace expansion happens before all other expansions.
🧩 7.1 Basic Forms
1 | |
🧩 7.2 Numeric Sequences
1 2 3 | |
🧩 7.3 Nested Braces
1 | |
🧩 7.4 Comma Lists
1 | |
🧩 7.5 Brace Expansion vs Globbing
Brace expansion is pure text generation, not pattern matching.
🧩 7.6 Brace Expansion vs Parameter Expansion
1 2 | |
8. Quote Removal
Quote removal happens after all expansions except brace expansion.
🧩 8.1 POSIX Rules
- double quotes preserve splitting/globbing suppression
- single quotes preserve everything literally
- backslash escapes characters
🧩 8.2 Bash Rules
Bash adds:
- ANSI C quoting:
$'...' - locale-aware escapes
$''inside arrays
🧩 8.3 Interaction with Splitting
1 2 | |
🧩 8.4 Interaction with Globbing
1 | |
→ literal *
1 | |
→ globbing
9. Arrays (Bash-only)
Arrays are one of Bash’s most powerful features.
🧩 9.1 Indexed Arrays
1 2 | |
🧩 9.2 Associative Arrays
1 2 | |
🧩 9.3 Array Slicing
1 | |
🧩 9.4 Array Expansion Rules
"${arr[@]}"
→ expands to N separate words
"${arr[*]}"
→ expands to one word, joined by IFS
🧩 9.5 Arrays in Command Substitution
1 | |
Dangerous — splitting + globbing.
🧩 9.6 Arrays in Arithmetic
1 | |
🧩 9.7 Arrays in Redirections
1 | |
10. Here-Docs & Here-Strings
Here-docs and here-strings are powerful input mechanisms with their own expansion rules.
🧩 10.1 Here-Doc Expansion Rules
Unquoted delimiter:
1 2 3 | |
→ expansions occur inside the body.
Quoted delimiter:
1 2 3 | |
→ no expansions occur.
🧩 10.2 Quoted vs Unquoted Delimiters
| Delimiter | Expansions | Interpretation |
|---|---|---|
<<EOF |
yes | normal expansions |
<<'EOF' |
no | literal text |
<<"EOF" |
yes | but quotes removed |
🧩 10.3 Here-Strings
1 | |
Equivalent to:
1 | |
🧩 10.4 Here-Docs in Pipelines
1 2 3 4 | |
Runs in a subshell in POSIX shells.
🧩 10.5 Here-Docs in Functions
Here-docs inside functions follow the same expansion rules.
11. Process Substitution (Bash/Zsh)
Process substitution creates temporary FIFOs or /dev/fd entries.
🧩 11.1 <(cmd)
Replaces with a filename:
1 | |
🧩 11.2 >(cmd)
Sends output to a process:
1 | |
🧩 11.3 FD Behavior
- creates pipes or FIFOs
- child processes inherit FDs
- can cause hangs if FDs leak
🧩 11.4 Subshell Behavior
Each substitution runs in a subshell.
🧩 11.5 Interaction with Pipelines
1 | |
Creates multiple parallel subshells.
🧩 11.6 Interaction with Expansions
Process substitution happens during command substitution phase.
12. Expansions in Redirections
Redirections undergo:
- parameter expansion
- command substitution
- arithmetic expansion
- pathname expansion
Example:
1 | |
13. Expansions in Pipelines
Each pipeline segment may run in a subshell:
1 | |
→ while runs in a subshell in POSIX shells.
14. Expansions in Functions
Functions expand arguments exactly like commands.
1 | |
15. Expansions in eval
eval performs:
- expansions
- splitting
- globbing
- evaluation
- expansions again
- execution
This is why eval is dangerous.
16. Expansions in Test Conditions
🧩 16.1 [ ] (POSIX)
- expansions occur
- splitting occurs
- globbing occurs
Example:
1 | |
→ globbing happens!
🧩 16.2 [[ ]] (Bash)
- no splitting
- no globbing unless intended
- safer
Example:
1 | |
→ pattern match, not globbing.
17. Expansions in Dockerfile ENTRYPOINT
ENTRYPOINT uses shell form:
1 | |
→ expansions occur in /bin/sh -c.
Exec form:
1 | |
→ no expansions.
18. Expansions in Kubernetes Manifests
Kubernetes does not expand shell variables. Only the shell inside the container does.
Example:
1 | |
19. Expansions in CI/CD Runners
CI runners often:
- use dash (not bash)
- disable brace expansion
- disable arrays
- disable process substitution
Scripts must be portable.
20. Expansions in SSH Remote Commands
1 | |
→ expansion happens on the remote host.
1 | |
→ expansion happens locally.
21. Expansions in cron/systemd
cron runs with:
- minimal PATH
- no user environment
- no shell rc files
Always use:
1 2 | |
22. Security Pitfalls
🧩 22.1 Unquoted Variables
1 | |
🧩 22.2 eval Injection
1 | |
🧩 22.3 Command Substitution Injection
1 | |
If $file contains backticks → code execution.
🧩 22.4 Globbing Injection
1 | |
23. Performance Engineering
🧩 23.1 Avoid command substitution in loops
Bad:
1 | |
Good:
1 | |
🧩 23.2 Prefer <file over $(cat file)
🧩 23.3 Avoid globbing in tight loops
🧩 23.4 Use arrays instead of splitting
🧩 23.5 Use mapfile for bulk reads
24. Debugging Expansions
🧩 24.1 Use set -x
Shows expanded commands.
🧩 24.2 Use PS4 for deep debugging
1 2 | |
🧩 24.3 Use printf '%q' to inspect quoting
🧩 24.4 Use declare -p to inspect variables
🧩 24.5 Use strace to inspect execve boundaries
25. Real‑World Failures
🧩 25.1 CI job deletes root directory
1 | |
🧩 25.2 JSON corruption due to newline stripping
🧩 25.3 Word splitting breaks arguments
🧩 25.4 Globbing expands unexpectedly
🧩 25.5 eval injection in production
26. Patterns
🧩 Quote everything by default
🧩 Use arrays for lists
🧩 Use printf for safe output
🧩 Validate variables before expansion
🧩 Avoid eval
🧩 Prefer [[ ]] over [ ]
🧩 Use process substitution for parallelism
🧩 Use here-docs for structured input
27. Anti‑Patterns
- Using
echofor structured data - Relying on implicit splitting
- Unquoted globs
- Using
lsfor parsing - Using eval with user input
- Using arrays via
arr=($(cmd)) - Using
for x in $list
🧠 Summary
Shell expansions form a deterministic, multi-phase compilation pipeline that transforms raw input into executable commands. Understanding expansions is essential for:
- correctness
- safety
- performance
- portability
- CI/CD reliability
- container entrypoints
- automation
- debugging
This module provides the complete, extended, production-grade reference for POSIX + Bash expansions.
Mastering expansions means mastering the shell.