Command substitution allows storing output of a Bash command in a variable for flexible scripting. This comprehensive reference will cover proper usage, best practices, limitations and pitfalls from an expert perspective.
Introduction
Bash is the most ubiquitous scripting language on UNIX-like systems used to automate interactions with the Linux shell and underlying API. Expert fluency in Bash is essential for Linux power users and system administrators.
Command substitution refers to syntax for running a command, capturing the standard output in a variable, and embedding that result within larger script logic. For example:
NOW=$(date +%F)
echo "Today‘s date is $NOW"
The $(cmd) style invocation is broadly preferred over backticks for readability. This guide will elaborate on proper usage.
Key reasons command substitution is invaluable in Bash scripts:
- Abstracts platform specific commands into variables
- Configures script logic dynamically based on system state
- Integrates Bash with outputs from external programs
- Orchestrates multi-step workflows by chaining commands
Contents
- Substitution Syntax and Usage
- Common Commands Used
- Accessing Stored Output
- Advanced Usage Techniques
- Use Cases and Applications
- Best Practices
- Debugging Substitutions
- Security Considerations
- Limitations and Anti-Patterns
- Conclusion
Let‘s dive in to unlocking the full potential of command substitution in Bash scripting.
Substitution Syntax and Usage
The syntax options for command substitution in Bash are:
$(command)
OR
`command`
For example, to store the current date:
TODAY=$(date +%F)
# Or
TODAY=`date +%F`
The $() style is considered more readable by convention. The backwards tick marks can cause confusion when nested, hence being disfavored.
Command substitutions return the standard output from the command. Many built-in Bash commands print their main result to standard output. Substitutions also capture exit codes and text written to error streams.
Common Commands Used
Commands commonly used in substitutions include:
Echoing
MSG="Hello!"
echo "Message is: $MSG"
Date and time
NOW=$(date +%T)
echo "Time now: $NOW"
Working directory
CWD=$(pwd)
echo "I‘m in: $CWD"
Listing directory contents
FILES=$(ls)
echo "Files: $FILES"
This pattern applies to most Bash built-ins and Linux commands that output text results.
Accessing Stored Results
To access the standard output captured from a command substitution, reference the variable storing it by name:
NAME=$(hostname)
echo "This server is called $NAME"
No { } braces are needed since it‘s a single variable rather than a phrase that could be ambiguous.
Newlines and other formatting are preserved as part of the value:
LS=$(ls)
echo "Files:
$LS"
Would print the ls output separated on newlines.
To execute a command stored in a substitution, refer to it by variable:
CMD="echo ‘Hi!‘"
$CMD # Runs the echo command
Next let‘s explore more advanced usage.
Advanced Usage Techniques
Command substitution enables several advanced scripting capabilities.
Conditional Logic
Test stored results in conditional statements to make decisions:
COUNT=$(ls | wc -l)
if [ $COUNT -gt 5 ]; then
echo "Many files here!"
fi

This evaluates whether the output of ls | wc -l exceeds 5 files.
Nested Substitutions
Nest substitutions to chain execution of several commands:
DIR=$(pwd)
FILES=$(ls $DIR)
echo "Contents: $FILES"
This first captures the working directory, then passes that path to ls to retrieve files.
Capturing Status Codes
Save exit codes to validate whether commands succeeded:
if ! RESULT=$(cmd 2>&1); then
echo "Failed with code $?"
fi
Here $? contains the exit code, while RESULT holds stderr.
Command Wrappers
Abstract complex commands into simple variables:
db_backup() {
mysqldump db | gzip > "$1"
}
LATEST=$(db_backup ‘db_0622.sql.gz‘)
This wraps a multi-step pipeline in a function named db_backup.
Use Cases and Applications
Capturing command output facilitates many scripting applications, like:
Configuration
Customize logic based on dynamic system state:
if [ "$(uname)" == "Darwin" ]; then
# Do something on MacOS
fi
Integration
Invoke external programs like version managers:
NVM_PATH=$(command -v nvm) # Get installation path
. $NVM_PATH # Load nvm
Automation
Link outputs between a series of operations:
DATA=$(generate_report)
MAIL_LIST=$(retrieve_emails)
notify_users "$DATA" "$MAIL_LIST"
This chains generating a report, getting relevant emails, and sending notifications.
Some other common use cases include:
- Checking whether dependencies are installed
- Validating sufficient disk space is available
- Migrating config file formats during app upgrades
- Distributing parallelizable workloads into subshells
Avoiding Pitfalls
While powerful, misuse of substitutions can lead to:
- Process forking overload – Each
$(cmd)forks a subshell process, becoming expensive in tight loops - Readability impacts – Deep nesting makes logic hard to understand
- Performance penalties – Repeated redundant calls instead of saving output
- Security issues – Unsanitized inputs substituted into sensitive commands
We‘ll revisit these pitfalls later. Next let‘s cover standards and best practices.
Best Practices
Follow these expert guidelines for clean, maintainable, and robust command substitution:
Style
- Strongly prefer
$()over legacy backticks for clarity - Use
{ }around complex variable names if needed
Naming
- Use descriptive names indicating a variable‘s purpose
- Prefix temporaries like
TEMP=$(cmd)
Validation
- Don‘t assume success – always check
$?after - Handle errors, exit codes, stderr properly
Security
- Call commands safely restricting environment as needed
- Validate inputs before substituting into commands
Legibility
- Break long chains into steps stored in temporary variables
- Wrap complex logic in functions instead of monolithic inline code
Adhering to these practices will improve script quality, security and reduce debugging time.
Debugging Substitutions
If issues arise with command substitutions, debug via:
Syntax Errors
If the command string isn‘t properly terminated or variable references are broken.
Validation
Print variable contents explicitly to check if they hold expected values:
echo "Directory was: $DIRNAME"
Tracing
If chained command output seems incorrect, print variables at each stage:
echo "Step 1 output: $OUT1"
echo "Step 2 output: $OUT2"
This exposes where divergent results enter.
Exit Codes
Check status and print $? after each substitution to catch failures:
if ! OUT=$(cmd); then
echo "Failed with exit code $?"
fi
Highlighters
Use editor IDE integrations highlighting Bash syntax to catch missing characters.
Dry Runs
Initially print substituted outputs vs using them to mutate state.
Rigorously applying these practices makes troubleshooting easier and builds logical deduction skills.
Security Considerations
Command substitutions carry security implications including:
Code injection
If concatenated into sensitive commands, inputs could alter meaning:
# UNSAFE
unescaped="; sudo rm -rf /"
CMD="echo $unescaped"
$CMD # Runs sudo deletion!
Exposure risk
Substitutions capture environmental variables. Output could reveal secrets:
LOG=$(env) # Captures env
echo "$LOG" # Prints secrets
Privilege escalation
Allowing users to specify command substitutions could enable running arbitrary commands.
Best practices to avoid these risks include:
- Avoid substitutions into security sensitive commands
- Scrub inputs of special chars like
; $|etc - Call commands with minimum required privileges
- Validate against allowed command whitelists
Treat substituted outputs themselves as sensitive, not exposing directly to users.
Limitations and Anti-Patterns
While powerful, command substitution has some key limitations and anti-patterns to avoid.
As mentioned before, misuses include:
Process forking overload
Each $(cmd) forks a subshell process. These accumulate rapidly:
for file in *; do
OUT=$(process "$file") # Thousands of processes!
done
This should batch, queue or find alternate approaches.
Readability impacts
Deep nesting obscures meaning:
DIR=$(echo $(dirname $(pwd))) # Too nested!
Performance penalties
Redundantly calling substituions instead of caching:
for x in {1..100}; do
NOW=$(date) # Inefficient
echo "$NOW"
done
Other languages more suited
Bash itself is limited without memory management, data structures, exceptions etc. Alternatives like Python open additional capabilities.
Mitigations:
- Refactor nested logic into functions
- Store frequently used outputs
- Profile process trees for hotspots
- Implement queues/messaging for concurrency
- Use languages like Python for advanced workflows
Understanding these limitations helps craft robust scripts as scale and complexity increase.
Conclusion
Command substitution is an invaluable technique for intermediates to advanced Bash scripters. Storing command output in variables enables:
- Abstracting platform specific calls into a variable
- Dynamically configuring logic based on system state
- Integrating Bash scripts with other utilities
- Orchestrating lengthy multi-step processes
We covered proper syntax, usage patterns, advanced applications demonstrateing the possibilities unlocked once this concept clicks.
Internalizing guidelines around security, performance, and anti-patterns ensures substitutions remain easy to maintain and reason about as scripts grow more sophisticated.
This guide only scratched the surface of Bash scripting capabilities. Explore other advanced functionality like process substitution, coprocesses, traps and handlers to further round out your expertise.
The journey to mastering Linux automation continues…but command substitution is a foundational milestone along that path!


