Command Substitution and Expansion
By the end of this lesson you'll be able to capture a command's output and drop it straight into another command, do integer math with arithmetic expansion, supply default values and measure string length with parameter expansion, feed two command outputs to a tool with process substitution, and avoid the classic bug of unquoted output splitting apart.
Learn Command Substitution and Expansion in our free Command Line course — an interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Cli course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
1. Command Substitution: $(command)
Command substitution runs a command and replaces the $(command) expression with that command's standard output (trailing newlines are stripped). The modern form is $(...) . There is also an older form that wraps the command in backtick characters , but $(...) is preferred because it reads more clearly and nests without extra escaping.
2. Storing Output in Variables and Arguments
The captured output is just text, so it works anywhere text does. Assign it with var="$(command)" to reuse the value, or drop a $(command) directly into another command's arguments. Quoting the substitution keeps multi-word output intact, which we will return to in the quoting section.
3. Nesting Substitutions
Because $(...) nests cleanly, you can place one substitution inside another and the shell evaluates the innermost one first. This is the headline advantage over the legacy backtick form, which would force you to backslash-escape the inner command.
4. Arithmetic Expansion: $((...))
Use $((...)) for integer math. It follows normal operator precedence, and inside the double parentheses you can reference variables without a dollar sign. Remember it is integer-only: $((7 / 2)) is 3 , and % gives the remainder.
5. Parameter Expansion: defaults and length
${ VAR:-default } expands to default when VAR is unset or empty , without changing VAR — perfect for fallbacks. ${ #VAR } expands to the length (character count) of the value, which is handy for validating input.
6. Process Substitution and Quoting
Process substitution <(command) hands a tool a filename that streams a command's output — ideal for diff <(cmd1) <(cmd2) with no temp files. And always wrap command substitution as "$(command)" : unquoted output is subject to word splitting and globbing, which silently mangles anything with spaces or newlines.
🎯 Your Turn: capture output into a variable
Fill in the one blank marked ___ using the # 👉 hint so the word count is captured with command substitution, then run it and check the Output panel matches.
🎯 Your Turn: default value then arithmetic
Supply a default for an unset variable, then do integer math on the result. Fill in the blank, then run it.
Pro Tips
- 💡 Always quote it: write "$(command)" , not bare $(command) , so whitespace and newlines in the output survive word splitting.
- 💡 Prefer $(...) over backticks: the dollar-paren form nests directly and visually pairs like brackets, while the backtick form needs awkward escaping to nest.
- 💡 No dollar signs inside $((...)) : arithmetic expansion reads variable names directly, so $((a + b)) is fine — no $a needed.
- 💡 Use defaults to harden scripts: ${ 1:-default } gives a missing argument a sensible value instead of letting it be empty.
Common Errors (and the fix)
- Output with spaces split into several arguments — you used bare $(command) . Quote it: "$(command)" to keep it as one value.
- Spaces around = in an assignment — x = $(cmd) fails. Assignment needs no spaces: x=$(cmd) .
- Expected a decimal but got a truncated number — $((...)) is integer-only, so $((7 / 2)) is 3 . Use a tool like bc for floating point.
- Nesting with backticks got confusing — the legacy backtick form does not nest cleanly. Switch to $(outer $(inner)) .
- ${ name:-x } still gave the old value — :- only kicks in when the variable is unset or empty; a set, non-empty variable keeps its value.
📋 Quick Reference
Task
Syntax
Notes
Capture output
$(command)
Preferred form
Legacy capture
backtick command backtick
Avoid; does not nest well
Store in variable
x="$(command)"
No spaces around =
Nest substitutions
$(outer $(inner))
Innermost runs first
Integer math
$((a + b))
No $ on names inside
Default value
${ VAR:-default }
If unset or empty
String length
${ #VAR }
Character count
Process substitution
diff <(c1) <(c2)
Output as a filename
Quote to be safe
"$(command)"
Prevents word splitting
Frequently Asked Questions
Mini-Challenge: a tiny text report
No blanks this time — just a brief and an outline. Combine command substitution, arithmetic expansion, and parameter expansion into a few commands that report on a fixed phrase, and check your output against the hints in the comments.
🎉 Lesson Complete!
- ✅ $(command) captures a command's output; it is preferred over the legacy backtick form and nests cleanly
- ✅ Store output with x="$(command)" and use it as variables or arguments
- ✅ $((...)) does integer arithmetic; variable names need no dollar sign inside
- ✅ ${ VAR:-default } supplies a fallback and ${ #VAR } gives the length
- ✅ <(command) is process substitution; always quote "$(command)" to prevent word splitting
- ✅ Next lesson: Redirection and File Descriptors — send output and errors to files with > , >> , 2> , and pipes
Practice quiz
What does command substitution do?
- Captures the output of a command and substitutes it into the command line
- Deletes a command from history
- Renames a command
- Replaces a command with another command on the PATH
Answer: Captures the output of a command and substitutes it into the command line. Command substitution runs a command and replaces the $(command) expression with that command's standard output.
Which is the modern, preferred syntax for command substitution?
- <command>
- %command%
- $(command)
- {command}
Answer: $(command). $(command) is the modern POSIX form. It nests cleanly and is preferred over the older backtick form.
Why is $(...) preferred over the older backtick form?
- It works only in zsh
- It nests cleanly and is easier to read, while the backtick form does not nest well
- It runs faster
- Backticks were removed from bash
Answer: It nests cleanly and is easier to read, while the backtick form does not nest well. $(...) can be nested directly and avoids the confusing backslash-escaping the backtick form needs when nested.
What is the result of the arithmetic expansion $((4 + 5 * 2))?
- 26
- 11
- 18
- 14
Answer: 14. $((...)) does integer arithmetic with normal precedence, so 5 * 2 is 10, plus 4 is 14.
What does ${name:-guest} expand to when name is unset or empty?
- guest
- An empty string
- An error
- The literal text name
Answer: guest. ${VAR:-default} uses the default value when VAR is unset or empty; otherwise it uses VAR's value.
If word=hello, what does ${#word} produce?
- 1
- 5
- hello
- h
Answer: 5. ${#VAR} expands to the length of the variable's value, and hello has 5 characters.
Why should you wrap command substitution in double quotes, as in "$(cmd)"?
- It makes the command run faster
- It is required for the command to run at all
- It converts the output to uppercase
- It prevents word splitting and globbing, preserving whitespace and newlines in the output
Answer: It prevents word splitting and globbing, preserving whitespace and newlines in the output. Quoting with "$(cmd)" stops the shell from splitting the output on whitespace and from expanding glob characters, keeping the result intact.
What does process substitution <(command) provide to the command using it?
- Nothing; it is a comment
- The exit status of command
- A filename that, when read, yields the output of command
- The process ID of command
Answer: A filename that, when read, yields the output of command. <(command) gives a filename (often /dev/fd/63) that streams the command's output, so tools expecting a file path can read it.
Which command compares the output of two commands without creating temporary files?
- diff $(cmd1) $(cmd2)
- diff <(cmd1) <(cmd2)
- diff ((cmd1)) ((cmd2))
- diff cmd1 cmd2
Answer: diff <(cmd1) <(cmd2). diff <(cmd1) <(cmd2) uses process substitution to feed each command's output to diff as a file.
What does count=$(grep -c root /etc/passwd) store in count?
- The number printed by the grep command
- The text grep -c root /etc/passwd
- The exit status of grep
- Nothing, because grep cannot be substituted
Answer: The number printed by the grep command. Command substitution in an assignment stores the command's standard output, so count receives the number grep -c printed.
Continue this course
- Previous: Globbing and Wildcards
- Next: Redirection and File Descriptors