Bash Command Substitution: Syntax, Examples and $() vs Backticks

Tested on RHEL 10.2 (Coughlan)
Package bash 5.2.26-6.el10 (core examples)
GNU Bash 5.3.0 built from upstream source (Bash 5.3+ section only)
Applies to Linux distributions with Bash installed
Privilege Normal user; examples create files in the current directory
Scope Bash command substitution with $() and backticks, including variables, quoting, nesting, newlines, exit status, and subshell behavior.
Related guides Shell scripting course hub
Short on time?Beta Get the gist of this 9-minute read in seconds, then ask follow-up questions

Bash command substitution lets you run a command and use its output inside another command or store it in a variable. The recommended syntax is $(command).

For example, today=$(date +%F) runs date +%F and stores its output in the today variable. Bash also supports the older backtick syntax, but $() is preferred because it is easier to read, quote, and nest.


What Does $() Mean in Bash?

$(command) performs command substitution: Bash runs command and replaces $(command) with its standard output before running the surrounding command.

You do not need a variable assignment first. Bash expands the substitution inline:

bash
echo "Today is $(date +%F)"

Bash runs date +%F first and inserts its result into the echo argument.

Three similar-looking forms are easy to confuse:

Syntax Meaning
$(command) Command substitution — run command, use its stdout
$((expression)) Arithmetic expansion — evaluate a numeric expression
${variable} Parameter expansion — expand a variable's value

Capture the short hostname into a variable:

bash
host=$(hostname -s)
printf 'host=%s\n' "$host"
output
host=vm1

Store Command Output in a Bash Variable

Assign command output with variable=$(command). In a simple variable assignment such as name=$(command), quoting the command substitution is not required to prevent word splitting. Quote the variable when you later use it as a command argument: "$name".

bash
today=$(date +%F)
printf 'today=%s\n' "$today"
output
today=2026-09-10

$() is defined by POSIX and is supported by Bash and other POSIX-compatible shells. Unlike some Bash-specific features later in this article, basic $(command) syntax is portable — see Bash vs sh when a script must stay portable beyond Bash-only features.


Bash Command Substitution Examples

These patterns show up constantly in scripts:

bash
today=$(date +%F)
host=$(hostname -s)
kernel=$(uname -r)
printf '%s on %s (%s)\n' "$today" "$host" "$kernel"
output
2026-09-10 on vm1 (6.12.0-211.47.1.el10_2.x86_64)

Use substitution inline without assigning first:

bash
echo "Running on $(hostname)"

Nest when one command needs another's output:

bash
parent=$(dirname "$(pwd)")
printf 'parent=%s\n' "$parent"

$() vs Backticks

Both forms capture stdout. Prefer $() in new scripts:

Feature $(command) Backticks
Modern / recommended Yes Legacy
POSIX Yes Yes
Nesting Easy Requires escaping
Quoting Easier Special escaping rules
Backslash handling Standard shell parsing Special rules inside backticks
New scripts Recommended Avoid

ShellCheck SC2006 recommends $() because backticks are hard to nest and have problematic quoting. GNU Bash documents special backslash treatment inside legacy backticks before $, backtick, and \.

Both forms work on Bash 5.2:

bash
from_backtick=`printf 'backtick'`
from_paren=$(printf 'paren')
printf 'backtick=%s paren=%s\n' "$from_backtick" "$from_paren"
output
backtick=backtick paren=paren

Nest Command Substitutions

You can nest command substitutions when one command needs the output of another.

Recommended form:

bash
path=/var/log/messages
parent=$(dirname "$(dirname "$path")")
printf 'parent=%s\n' "$parent"
output
parent=/var

The same path with legacy backticks needs escaped inner backticks:

bash
parent=`dirname "\`dirname "$path"\`"`
printf 'parent=%s\n' "$parent"
output
parent=/var

Quote inner expansions such as "$(dirname "$path")" so paths with spaces stay intact.


Quote Command Substitution Results

Word splitting applies when an unquoted substitution appears as a command argument. Bash single vs double quotes covers the full quoting rules; for command substitution, quote the expansion when the captured text must stay one word.

Capture a name that contains spaces:

bash
name=$(printf 'my report.txt')
printf '<%s>\n' $name
output
<my>
<report.txt>

Unquoted $name was split into two words because the space became a word-splitting boundary. Quote it when you need the complete value to remain one argument:

bash
printf '<%s>\n' "$name"
output
<my report.txt>

In a simple variable assignment such as name=$(command), quoting the command substitution is not required to prevent word splitting. Quote "$name" when you pass the value to another command.


What Happens to Newlines?

Bash removes all trailing newline characters from command-substitution output. Embedded newlines remain in the string until unquoted word splitting breaks them apart.

bash
value=$(printf 'hello\n\n')
printf '<%s>\n' "$value"
printf 'length=%s\n' "${#value}"
output
<hello>
length=5

The two trailing newlines were removed, so the variable contains only hello, which is five characters long.

Capture output that contains an internal newline:

bash
multi=$(printf 'a\nb\n')
printf 'multi=%q\n' "$multi"
output
multi=$'a\nb'

Unquoted substitution can make it look like newlines vanished entirely:

bash
echo $(printf 'a\nb\n')
output
a b

Command substitution removed only the trailing newline. The embedded newline remained initially, but because the substitution was unquoted, word splitting treated it as a delimiter. echo then printed the resulting arguments separated by a space. Keep "$multi" quoted when line breaks must survive.


Does Command Substitution Capture stderr?

Command substitution captures stdout only. Errors on stderr still print to the terminal unless you redirect them.

Run ls on a path that does not exist:

bash
output=$(ls /does-not-exist)

The ls: cannot access … message appears on stderr in the terminal. The output variable stays empty because stderr was not part of the substitution.

Merge stderr into stdout when you want both in the variable:

bash
output=$(ls /does-not-exist 2>&1)
status=$?
printf 'out=%q status=%s\n' "$output" "$status"
output
out=ls:\ cannot\ access\ \'/does-not-exist\':\ No\ such\ file\ or\ directory status=2

The exact wording of the ls error can vary by locale or coreutils version.

To silence errors while keeping stdout capture, redirect stderr away: output=$(command 2>/dev/null).


Command Substitution Runs in a Subshell Environment

Standard $() runs the nested commands in a subshell. Variable assignments inside the substitution do not affect the parent shell.

bash
DEMO_SIDE=1
captured=$(DEMO_SIDE=2; printf '%s' "$DEMO_SIDE")
printf 'parent=%s captured=%s\n' "$DEMO_SIDE" "$captured"
output
parent=1 captured=2

The same rule applies to cd:

bash
pwd_before=$(pwd)
inside=$(cd /tmp && pwd)
pwd_after=$(pwd)
printf 'before=%s\ninside=%s\nafter=%s\n' "$pwd_before" "$inside" "$pwd_after"
output
before=/tmp/cmdsub-lab
inside=/tmp
after=/tmp/cmdsub-lab

cd /tmp ran inside the command substitution's subshell. It produced inside=/tmp, but the parent shell stayed in /tmp/cmdsub-lab.


How to Capture Command Output and Exit Status

$? reflects only the last command. The shortest useful pattern:

bash
output=$(command)
status=$?

Apply it to a real command:

bash
printf 'ok\n' > status-demo.txt
output=$(grep '^ok' status-demo.txt)
status=$?
printf 'match output=%q status=%s\n' "$output" "$status"
output
match output=ok status=0

When the pattern is missing, the substitution is empty but the status still reports failure:

bash
output=$(grep '^missing' status-demo.txt 2>/dev/null)
status=$?
printf 'miss output=%q status=%s\n' "$output" "$status"
output
miss output='' status=1

Avoid local output=$(some_command) when you need some_command's exit status — local can mask it (ShellCheck SC2155). Declare and assign separately:

bash
my_func() {
  local output
  output=$(grep '^missing' status-demo.txt 2>/dev/null)
  printf 'status=%s\n' "$?"
}

The same pattern is covered in Bash functions. For status semantics in scripts, see Bash exit codes.


Read a File with $(<file)

Bash provides $(<file) as a faster shorthand for capturing a file's contents without starting an external cat process. It is Bash-specific; POSIX $() itself is portable, but this read shortcut is not.

bash
printf 'file contents\n' > sample.txt
via_read=$(<sample.txt)
via_cat=$(cat sample.txt)
printf 'read=%q cat=%q\n' "$via_read" "$via_cat"
output
read=file\ contents cat=file\ contents

GNU Bash documents that $(cat file) can be replaced by the equivalent but faster $(< file). Trailing-newline removal still applies. For many lines or large files, prefer Bash mapfile instead of one giant string.


Common Command Substitution Mistakes

Command substitution is intended for text. Bash variables cannot store NUL (\0) bytes, so do not use $(command) to capture arbitrary binary data.

Symptom Likely cause Fix
status=0 after a failing inner command in a function local x=$(cmd) masks the inner status Declare local x and assign on the next line (SC2155)
One value becomes several arguments Unquoted $(command) as an argument Use "$(command)"
Multiline output becomes many words Unquoted substitution + default IFS Quote the result or parse line by line
Errors print but variable is empty stderr is not captured Add 2>&1 or redirect stderr explicitly
Parent variable unchanged after $(var=1; …) Subshell isolation with $() Expected with $(); Bash 5.3 ${ command; } can be used when current-shell side effects are intentionally required
Binary data corrupted NUL bytes in stdout Use a file or stream, not a Bash string

Bash 5.3: Command Substitution in the Current Shell

GNU Bash 5.3 added two alternate forms. They are not available in Bash 5.2, including the Bash version shipped with RHEL 10 at the time of testing. Examples below used GNU Bash 5.3.0 built from upstream source.

Form Behavior
${ command; } Captures stdout; runs in the current shell; trailing newlines removed
`${ command; }`

Check the version before using either form:

bash
printf 'BASH_VERSINFO=%s.%s.%s\n' "${BASH_VERSINFO[0]}" "${BASH_VERSINFO[1]}" "${BASH_VERSINFO[2]}"

Side effects persist with ${ command; }:

bash
B53_SIDE=1
val=${ printf '%s' "$B53_SIDE"; B53_SIDE=2; }
printf 'B53_SIDE=%s val=%s\n' "$B53_SIDE" "$val"
output
B53_SIDE=2 val=1

${| command; } expands from REPLY and can preserve a trailing newline:

bash
text=${| REPLY=$'line\n'; }
printf 'text=%q len=%s\n' "$text" "${#text}"
output
text=$'line\n' len=5

Use these forms only when you know the script will run on Bash 5.3 or later — there is no feature toggle to turn them on. For portable scripts, stay with $(command).

Need Use
Portable capture on Bash 5.2 / POSIX sh $(command)
Side effects must not leak to parent $(command)
Changes inside substitution should persist (Bash 5.3+) ${ command; }
REPLY with trailing newline preserved (Bash 5.3+) `${

Command Substitution vs Process Substitution

Syntax Purpose
$(command) Capture output as text
<(command) Expose output as file-like input
>(command) Send output into a command

result=$(command) stores stdout in a variable. diff <(cmd1) <(cmd2) feeds each command's output to diff through a file descriptor without storing the full stream in a string first. When you need every line in an array, use mapfile with process substitution instead of one large $(…) capture.


Summary

Bash command substitution runs a command and inserts its stdout with $(command) or legacy backticks. Prefer $() for nesting, quoting, and readability. Store output in a variable with var=$(command), or expand inline with echo "Today is $(date +%F)".

Substitution captures stdout only — redirect stderr with 2>&1 when errors belong in the variable. Quote "$(command)" when the result is used as a single command argument; trailing newlines are stripped from the capture, while embedded newlines need quoting to survive word splitting. Standard $() runs in a subshell, so cd and variable changes inside it do not persist.

Capture output and exit status with output=$(cmd) followed by status=$?, and split local from assignment when the inner status matters. GNU Bash 5.3 adds ${ command; } and ${| command; } as current-shell alternatives to standard command substitution — guard their use by requiring Bash 5.3+. For where command substitution sits in the expansion pipeline, see Bash shell expansions.


References


Frequently Asked Questions

What is the difference between $() and $(( )) in Bash?

$(command) is command substitution — Bash runs command and uses its stdout. $((expression)) is arithmetic expansion — Bash evaluates a numeric expression such as $((n + 1)).

Does Bash command substitution capture stderr?

No. Only stdout is captured. stderr still prints to the terminal unless you redirect it, for example with output=$(command 2>&1).

Why are trailing newlines removed?

GNU Bash deletes trailing newline characters from command-substitution output before inserting the result. Internal newlines in the captured text are kept until later unquoted word splitting.

Do backticks still work?

Yes. Backticks are the legacy form. New scripts should use $() because nesting and quoting are simpler.

How do I capture both output and exit code?

Run output=$(cmd) then status=$? on the next line. Do not combine local output=$(cmd) when you need the inner exit status.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)