$(...) vs Backticks: Why Modern Bash Prefers the Former
Command substitution replaces $(command) (or the legacy ` command backtick form) with that command's standard output, with any trailing newlines stripped. The $(...) syntax, standardized by POSIX, nests cleanly — $(cmd1 $(cmd2)) — without the escaping headaches backticks require, since nested backticks need each inner backtick escaped with a backslash, quickly becoming unreadable. $(...) also visually distinguishes itself from literal text better than backticks, which are easy to mistake for single quotes in many fonts, making $(...)` the recommended style in virtually every modern shell style guide.
Cricket analogy: Nesting $(cmd1 $(cmd2)) cleanly is like a scorer nesting a player's strike rate calculation inside their overall match rating formula without needing awkward workarounds, unlike trying to nest two backtick-quoted stats which would require escaping.
Quoting Rules: Why $(command) Needs Double Quotes
Unquoted, $(command) undergoes word splitting on $IFS and pathname expansion just like an unquoted variable, so for f in $(ls *.txt) can break on filenames containing spaces and files=$(find . -name '*.log') followed by rm $files can accidentally expand globs in unexpected ways. Wrapping it in double quotes, "$(command)", preserves internal newlines and spaces exactly as the command produced them, which is essential when capturing multi-line output like git log or a file listing into a single variable meant to be printed or compared as one block of text. The one common exception is inside [[ ]] or when the substitution is intentionally meant to split into multiple words, such as building an array with read -ra arr <<< "$(cmd)".
Cricket analogy: Quoting "$(command)" to preserve a multi-line scorecard is like keeping a full over-by-over commentary transcript intact as one block, rather than letting it fragment into scattered individual words that lose the sequence.
Capturing Exit Status and Multi-Line Output
Command substitution runs in a subshell, so any variables set inside $(...) don't persist outside it, and the substitution itself evaluates to the captured stdout, not the command's exit status — you must check $? immediately after the assignment if the exit code matters, since assigning var=$(cmd) sets $? to cmd's exit status (not the assignment's). Because trailing newlines are always stripped from the captured output, but internal newlines are preserved when quoted, output=$(printf 'a\nb\n') followed by echo "$output" prints a then b on two lines, while echo $output unquoted collapses them onto one line separated by a space.
Cricket analogy: Checking $? right after result=$(run_drs_check.sh) is like the third umpire immediately confirming the review's verdict flag the moment it's returned, before any other decision overwrites it.
#!/usr/bin/env bash
set -euo pipefail
# Preferred $(...) syntax, safely quoted
current_branch="$(git rev-parse --abbrev-ref HEAD)"
echo "On branch: $current_branch"
# Multi-line capture preserved with quoting
changed_files="$(git diff --name-only HEAD~1)"
if [[ -n "$changed_files" ]]; then
echo "Changed files:"
echo "$changed_files"
fi
# Checking exit status of the captured command
if output="$(curl -sf https://api.example.com/health)"; then
echo "Health check passed: $output"
else
status=$?
echo "Health check failed with exit code $status" >&2
exit "$status"
fi
# Intentional word-splitting: building an array from command output
read -ra files_array <<< "$(ls *.log 2>/dev/null)"$(...) and backticks both strip *trailing* newlines from the captured output, but they behave identically on that point — the real reason to prefer $(...) is nesting clarity and readability, not newline handling. If you need to preserve a genuinely trailing newline, append a sentinel character before capture and strip it afterward.
$(command)captures stdout as a string with trailing newlines stripped; prefer it over legacy backticks for clean nesting.- Always quote command substitution —
"$(command)"— to prevent word splitting and glob expansion on the result. - Internal newlines are preserved only when the substitution is quoted; unquoted, they collapse into spaces.
- Command substitution runs in a subshell, so variables assigned inside
$(...)don't persist outside it. var=$(cmd)sets$?tocmd's exit status — check it immediately if success/failure matters.- Nested
$(cmd1 $(cmd2))reads cleanly, unlike nested backticks which require escaping inner backticks. - Intentional unquoted substitution is occasionally useful, e.g. splitting output into array elements via
read -ra.
Practice what you learned
1. Why is `$(command)` generally preferred over backtick command substitution?
2. What happens to trailing newlines when capturing output with $(command)?
3. Why should `files=$(find . -name '*.log')` be quoted when used later, as in `rm "$files"`... wait, why quote when using $files at all?
4. After `result=$(some_command)`, what does `$?` reflect?
5. Does command substitution run in a subshell?
Was this page helpful?
You May Also Like
Process Substitution
Learn how Bash's <(...) and >(...) process substitution let commands read from or write to another command's output as if it were a file.
Advanced Loop Patterns
Go beyond basic for loops with C-style iteration, while-read idioms for safe line processing, and loop control with break, continue, and select.
Advanced Conditionals and Test Operators
Master Bash's test, [ ], and [[ ]] constructs with string, numeric, and file test operators to write robust, bug-free conditional logic.
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