Command Substitution in Bash: $(...) vs. Backticks and Their Pitfalls
AI generated
$_
#!/
Bash · Scripting · Subshells · Linux
Command Substitution in Bash
$(...) vs. backticks and the pitfall they both share

$(...) and backticks appear to produce the same result at first glance: a command's output gets substituted somewhere else in the script. The difference only shows up with nesting, quoting, and understanding why a variable set inside the substitution vanishes without a trace afterward. This article covers both.

15 min read $(...) · Backticks · Subshells Bash 4.x · 5.x · POSIX sh

1. What command substitution does and why two notations exist

Command substitution replaces an expression in a script with the standard output of the command inside it. Bash runs that command in its own subshell, collects everything it writes to standard output, strips trailing newlines, and inserts the result where the substitution stood in the surrounding command. Two notations achieve the same result: the modern $(command) and the older backtick notation inherited from the Bourne shell.

Historically, backticks came first, $(...) was added with the Korn shell and later became part of POSIX. Both forms are equally available and functionally identical in any modern Bash today, as long as there is no additional need for nesting or complex quoting. That is exactly where the two notations part ways.

2. $(...): syntax and basic behavior

$(command) starts a subshell in which command runs completely as its own script, with its own environment as a copy of the calling process. Bash collects everything written to standard output as the result of the substitution, automatically stripping one or more trailing newline characters at the end, while newlines within the text itself are preserved.

Because $(...) opens and closes a unique pair of parentheses, Bash's parser can unambiguously recognize the content as a nested context in which quotes, escape characters, and even further $(...) constructs get reinterpreted independently of the surrounding context, similar to nested parentheses in a programming language.


current_branch=$(git rev-parse --abbrev-ref HEAD)
echo "Branch: $current_branch"

files_changed=$(git diff --name-only "$current_branch" main | wc -l)
echo "Files changed: $files_changed"

3. Backticks: syntax and their escaping quirks

The backtick notation `command` produces the same result as $(command) in the simplest case, but does not use a unique pair of brackets, only two identical backtick characters as start and end markers. Bash's parser therefore has to recognize the second backtick purely textually as the end, which introduces special rules for any contained backslash that differ from the behavior inside $(...).

A backslash before a dollar sign, another backtick, or a backslash itself gets interpreted once already at the backtick level before the actual command ever sees it, leading to behavior many Bash users find unintuitive and that simply does not happen with $(...), because there each level cleanly owns its own bracket.


# Identical in simple cases
echo `date +%F`
echo $(date +%F)

# Differ once a backslash is involved
echo `echo \\$HOME`   # prints $HOME, not the path
echo $(echo \$HOME)     # also prints $HOME, but more consistently readable

4. Nesting: why $(...) nests cleanly and backticks do not

Because $(...) has a unique opening and closing character, the parser correctly resolves arbitrarily deeply nested calls like $(command1 $(command2 $(command3))), each level gets its own pair of brackets and stays visually traceable for the reader. Editors with bracket highlighting can also visualize this structure automatically, which considerably eases debugging in complex scripts.

Backticks, by contrast, require escaping the inner backticks with a backslash at every nesting level so the parser can tell them apart from the outer level, for example `command1 \`command2\``. Past two nesting levels this expression becomes barely readable, and past three levels it is effectively unmaintainable in practice, which is why backticks are unsuitable for anything beyond the simplest cases.

5. Why $(...) is almost always the better choice

Beyond nesting, several other reasons favor $(...): in many fonts a backtick is hard to distinguish visually from a plain single quote, which invites copy-paste errors from formatted documents or chat messages, whereas $( and ) are visually unambiguous. On top of that, virtually every common linter such as ShellCheck treats $(...) as the recommended form and explicitly flags backticks as outdated.

ShellCheck consistently flags backticks with rule SC2006, recommending they be replaced with $(...), and most style guides for production Bash scripts, such as the Google Shell Style Guide, explicitly ban backticks. Anyone writing new Bash code today should use $(...) without exception and only recognize backticks as equivalent but outdated when reading older scripts.

6. The shared side effect: command substitution always runs in a subshell

Regardless of whether $(...) or backticks are used, Bash runs the contained command in its own subshell in both cases, a child process with a copy of the environment at the time of the call. Every variable assignment, every cd call, and every shell option change made inside that substitution only affects the subshell and is lost the moment the substitution finishes and the child process exits.

This side effect surprises many people who try to fit a counter increment or a global status variable update inside a command substitution, for example wrongly assuming result=$(counter=$((counter+1)); echo done) would increment the outer counter variable. In reality only the copy inside the subshell increments, the outer variable stays unchanged, a behavior identical to the subshell problem seen with pipes.


counter=0
result=$(counter=$((counter+1)); echo "inside: $counter")
echo "$result"
echo "outside: $counter"
# inside: 1
# outside: 0   <- the change was lost together with the subshell

7. Practical pitfalls: word splitting and the swallowed newline

An unquoted $(command) is subject to word splitting and pathname expansion, exactly like an unquoted variable, so output containing spaces or glob characters can unexpectedly break apart into multiple arguments or expand against existing files. The rule is therefore the same as with variables: wrap "$(command)" in double quotes, unless splitting into multiple words is explicitly desired.

A second surprise concerns multi-line output: command substitution only strips trailing newlines at the very end of the whole output, not newlines in the middle. Anyone capturing the output of ls or find and expecting it to automatically become a space-separated list overlooks that newlines still sit inside it, only getting broken into separate words once it is inserted unquoted and undergoes word splitting.


# Unquoted: word splitting breaks the output apart
files=$(find . -maxdepth 1 -name "*.log")
for f in $files; do echo "processing: $f"; done   # breaks on spaces in names

# Quoted and with mapfile: robust against spaces in filenames
mapfile -t files < <(find . -maxdepth 1 -name "*.log")
for f in "${files[@]}"; do echo "processing: $f"; done

8. When to avoid command substitution

Every command substitution starts at least one extra process for the subshell itself and typically another for the contained command, which noticeably costs time inside a tight loop running thousands of iterations. A result=$(echo "$x" | tr 'a-z' 'A-Z') inside a loop with 10000 iterations spawns 20000 extra processes, while the same task using the built-in parameter expansion ${x^^} needs no external process at all.

As a rule of thumb: for individual calls, the process overhead is irrelevant and the readability of command substitution outweighs any performance concern. Inside loops with a high iteration count, though, it pays to check whether a native Bash parameter expansion, an array, or a single call outside the loop can produce the same output without spawning a new subshell on every iteration.

9. $(...) and backticks compared directly

Both notations produce identical results in simple cases but differ significantly in nesting, readability, and tooling support. The table below summarizes the decision criteria for new Bash code.

Criterion $(...) Backticks Recommendation
Nesting Arbitrarily deep, clearly readable Requires backslash escaping per level $(...) for any nesting
Readability in an editor Unambiguous brackets Easily confused with quotes Prefer $(...)
ShellCheck compliance Recommended form SC2006, flagged as outdated $(...) for new code
Subshell behavior Identical to backticks Identical to $(...) No difference, applies to both

Mironsoft

Shell automation, DevOps tooling and deployment infrastructure

Shell scripts that hold up in production?

We review existing Bash scripts, spot fragile patterns and replace them with robust Bash patterns: complete error handling, logging and safe parallelization for your deployment stack.

Code Review

ShellCheck analysis and manual review for critical Bash pattern violations.

Refactoring

Retrofitting error handling, logging and safe file operations.

CI Integration

Wiring ShellCheck and BATS into pipelines and building regression tests.

10. Summary

Command Substitution: The Essentials at a Glance

Basic behavior

$(command) and `command` both produce the command's standard output, with trailing newlines stripped.

Nesting

$(...) nests arbitrarily deep, backticks require backslash escaping from the second level onward and quickly become unreadable.

Subshell trap

Variable changes inside a command substitution are lost because they run inside their own subshell.

Recommendation

Use $(...) exclusively for new code, only recognize backticks as equivalent but outdated when reading older scripts.

11. FAQ: Command Substitution: The Essentials at a Glance

1What is the difference between $(...) and backticks?
Both run a command and produce its standard output as the result. $(...) has unambiguous brackets and nests arbitrarily deep, backticks require backslash escaping from the second nesting level onward.
2Why is $(...) recommended over backticks?
Because $(...) nests cleanly, is visually unambiguous, and is flagged by linters like ShellCheck as the modern, recommended form. Backticks are considered outdated.
3Why does a variable I set inside $(...) disappear?
Because command substitution always runs in its own subshell. Changes to variables inside that subshell do not affect the calling shell.
4Can I nest $(...) arbitrarily deep?
Yes, because every $(...) has its own unique pair of brackets, the parser can cleanly tell any number of levels apart.
5What happens if I need to nest backticks?
The inner backticks must be escaped with a backslash. Past two levels the expression becomes barely readable, which is why $(...) is clearly preferable here.
6Should I always quote the result of $(...)?
Yes, otherwise the output is subject to word splitting and pathname expansion, which leads to unexpected results whenever the output contains spaces or glob characters.
7Does command substitution strip all newlines?
No, only trailing newlines at the very end of the whole output. Newlines inside the output are preserved until unquoted word splitting breaks them apart.
8Is command substitution slow?
Every call spawns at least one extra subshell. That is irrelevant for individual calls, but the overhead can become noticeable inside loops with many iterations.
9What does ShellCheck flag about backticks?
Rule SC2006 explicitly recommends replacing backticks with $(...), because the latter nests and reads better.
10Are there cases where backticks still make sense?
Practically not for new code. They still show up in very old scripts or minimal POSIX sh environments where $(...) was historically unavailable, which is rarely relevant today.