Nested Command Substitution: Backticks Fail Where $() Succeeds
Why Backticks Fail in Nested Command Substitution
Command substitution allows you to run commands and capture their output to be used as an argument or part of a larger command. The output text substitutes the command substitution construct when the command is run. The most common forms of command substitution are using backticks (`) or $().
For example:
today=`date +%F`
The date
command is run and its output is assigned to the today
variable. This works at one level, but fails when nested:
today=`echo `date +%F``
The nested backticks are evaluated as empty strings, so it fails. The $() form allows nesting without issues:
today=$(echo $(date +%F))
The innermost substitution is evaluated first, with the output passed to the outer command substitution. This demonstrates why backticks fail at nesting while $() handles it correctly.
The Fundamental Difference Between $() and Backticks
The issue with nested backticks stems from how $() and backticks are implemented differently:
- $() substitutions run commands in a subshell environment. This means a new child process is forked to evaluate the nested command. So the inner $() has its own isolated subprocess. This allows arbitrary depths of nesting.
- Backticks use command substitution to substitue the stdout of a command in place. No subshell is created. So when nested, the inner backticks get substituted as empty strings rather than running the intended command.
In essence, $() is evaluated in a separate context from its parent while backticks try to evaluate nested commands in place. This difference is what allows $() to handle nesting while backticks cannot.
When to Use $() Over Backticks
Given the nesting limitations with backticks, best practice is to standardize on $() for command substitution, even when not nesting. Reasons to always prefer $() include:
- Portability – $() works across all POSIX shells, while backtick support can vary.
- Readability – Nested $() is easier to read and parse visually.
- Consistency – Using only $() avoids confusion on when nesting is supported.
- Nested Evaluation – Even non-nested use will ensure support for nesting.
For example, while this non-nested case would work with backticks:
now=`date +%s`
Using $() makes it consistent:
now=$(date +%s)
And allows trivial nesting later without changing syntax:
timestamp=$(echo $(date +%s))
Workarounds for Using Backticks
There may be some legacy scripts or environments that still rely on backticks for substitution. While substituting $() is best where possible, there are some workarounds to enable nested backtick usage if absolutely required:
- Use an intermediate variable – Assign the inner command to a temp variable, then reference that variable in the outer backticks.
- Escape the inner backticks – Put a \ before the inner backticks so they are treated literally instead of substituted.
- Evaluate the nested command first – Split your command over multiple lines, running the inner command separately before the outer one.
For example, this would allow nested backticks by using a temp variable tmp:
tmp=`date +%F` output=`echo $tmp`
But this approach is clunky, hard to maintain, and easy to break. Using $() consistently is still the best approach.
Example Use Cases Requiring Nested Substitution
While simple single-level command substitution is most common, there are more complex cases where nested substitution is required or beneficial:
Complex Command Pipelines
In complex multi-stage shell pipelines, one command’s output may feed into subsequent commands requiring additional substitution:
formatted=$(echo $(date +%F | tr '-' '/') | wc -c)
Here a date is reformatted and piped to output its length in characters. This requires nested $() around each pipeline component.
Conditional Logic in Scripts
Scripts may use nested substitution to evaluate conditional logic and determine runtime control flow. For example:
if [[ "$(pwd)" == $(echo $HOME) ]]; then echo "We are in the user's HOME folder" fi
Here nested $() compares the command output to evaluate the if
condition.
Dynamic Inputs or Arguments
Generating dynamic arguments to pass into commands also benefits from nested substitution:
mkdir -p $(echo $(date +%Y)/$(date +%m)/$(date +%d))
This uses three levels of date formatting nested substitution to create a dynamic folder path for mkdir
to use based on runtime date values.
Summary and Best Practices
In summary, nested command substitution is enabled through the POSIX shell $() syntax but fails when attempted with backticks due to how they are evaluated differently:
- $() supports arbitrary nesting by running each substitution in a subshell environment
- Backticks fail on nests because they try to substitute output text directly in place
Key takeaways are:
- Always prefer $() over backticks even when not nesting commands
- This ensures consistency, portability, and keeps the option for nested evaluation
- If backticks are unavoidable, use intermediate variables, escapes, and separate commands to workaround limitation
- Common nested command cases involve conditional script logic, command pipelines, dynamic arguments
By understanding why nesting fails with backticks but is fully supported with $(), developers can write more robust bash scripts.