Advanced Bash Scripting

Expansions Part Two

Command Substitution

Command substitution is a fundamental feature of Bash scripting that lets you capture the output of one command and embed it into another. This technique improves script readability and enables dynamic data handling in your shell scripts.

Recap: Variable Expansion

Use $ followed by a variable name to expand its value. Enclose the name in curly braces {} to avoid ambiguity, and wrap expansions in quotes to prevent word splitting and globbing.

name="John"
echo "${name}"
# Output: John

Tip

Always quote your variable expansions ("${var}") to preserve whitespace and avoid unexpected globbing.

What Is Command Substitution?

Command substitution runs a command in a subshell and replaces the command with its standard output. There are two syntaxes:

SyntaxDescription
$( ... )Preferred form; allows nesting easily.
`...`Deprecated; harder to read and nest.

Example: Counting Files on the Command Line

$ ls
DEV001  DEV002  DEV003  DEV004  MKT001  MKT002  MKT003  MKT004  command-substitution.sh
$ find . -type f | wc -l
9

Using $( ... )

Create command-substitution.sh:

#!/usr/bin/env bash
file_count=$(find . -type f | wc -l)
echo "Total files: ${file_count}"

Run it:

$ chmod +x command-substitution.sh
$ ./command-substitution.sh
Total files: 9

Using Backticks (Deprecated)

#!/usr/bin/env bash
file_count=`find . -type f | wc -l`
echo "Total files: ${file_count}"

Warning

Backticks are deprecated. They complicate nesting and reduce readability. Always prefer $( ... ) in modern Bash scripts.

Accepting a Directory Argument

Require the user to specify a target directory:

#!/usr/bin/env bash
if [[ -z "${1}" ]]; then
  echo "Usage: $0 <directory>"
  exit 1
fi

file_count=$(find "${1}" -type f | wc -l)
echo "Files in ${1}: ${file_count}"
$ ./command-substitution-v2.sh .
Files in .: 9

Timing Considerations

Command substitution runs once when assigned. If you modify files later, the stored value stays unchanged until you reassign it.

#!/usr/bin/env bash
if [[ -z "${1}" ]]; then
  echo "Usage: $0 <directory>"
  exit 1
fi

file_count=$(find "${1}" -type f | wc -l)
echo "Initial count: ${file_count}"

touch samplefile
echo "After touch: ${file_count}"
$ ./command-substitution-v3.sh .
Initial count: 9
After touch: 9

Re-running the script updates the count:

$ ./command-substitution-v3.sh .
Initial count: 10
After touch: 10

Subshell Scope

Command substitution executes in a subshell. Variables modified inside do not affect the parent shell:

#!/usr/bin/env bash
if [[ -z "${1}" ]]; then
  echo "Usage: $0 <directory>"
  exit 1
fi

dir=${1}
file_count=$(find "${dir}" -type f | wc -l)

sub_output=$(
  dir="/some/other/dir"
  echo "Dir in subshell: ${dir}"
)

echo "Dir in parent shell: ${dir}"
echo "${sub_output}"
$ ./command-substitution-v4.sh /usr/bin
Dir in parent shell: /usr/bin
Dir in subshell: /some/other/dir

The image explains that a subshell is a child process spawned by a parent shell, inheriting environment variables but not propagating them back to the parent shell.

A subshell inherits your environment but keeps its changes local.

Performance Considerations

Each command substitution spawns a new process. In most scripts this overhead is negligible, but in performance-critical loops, minimize unnecessary substitutions.

Performance Warning

Avoid placing heavy command substitutions inside tight loops. Consider alternatives like readarray or built-in string operations when processing large datasets.

Honorable Mentions

Use CaseExample
Capture stderrfiles=$(ls -j 2>&1)
Capture timestampcurrent_date=$(date)
echo "Errors: ${files}"
echo "Today's date: ${current_date}"

Watch Video

Watch video content

Previous
Brace