| 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 |
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:
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:
host=$(hostname -s)
printf 'host=%s\n' "$host"host=vm1Store 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".
today=$(date +%F)
printf 'today=%s\n' "$today"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:
today=$(date +%F)
host=$(hostname -s)
kernel=$(uname -r)
printf '%s on %s (%s)\n' "$today" "$host" "$kernel"2026-09-10 on vm1 (6.12.0-211.47.1.el10_2.x86_64)Use substitution inline without assigning first:
echo "Running on $(hostname)"Nest when one command needs another's output:
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:
from_backtick=`printf 'backtick'`
from_paren=$(printf 'paren')
printf 'backtick=%s paren=%s\n' "$from_backtick" "$from_paren"backtick=backtick paren=parenNest Command Substitutions
You can nest command substitutions when one command needs the output of another.
Recommended form:
path=/var/log/messages
parent=$(dirname "$(dirname "$path")")
printf 'parent=%s\n' "$parent"parent=/varThe same path with legacy backticks needs escaped inner backticks:
parent=`dirname "\`dirname "$path"\`"`
printf 'parent=%s\n' "$parent"parent=/varQuote 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:
name=$(printf 'my report.txt')
printf '<%s>\n' $name<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:
printf '<%s>\n' "$name"<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.
value=$(printf 'hello\n\n')
printf '<%s>\n' "$value"
printf 'length=%s\n' "${#value}"<hello>
length=5The two trailing newlines were removed, so the variable contains only hello, which is five characters long.
Capture output that contains an internal newline:
multi=$(printf 'a\nb\n')
printf 'multi=%q\n' "$multi"multi=$'a\nb'Unquoted substitution can make it look like newlines vanished entirely:
echo $(printf 'a\nb\n')a bCommand 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:
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:
output=$(ls /does-not-exist 2>&1)
status=$?
printf 'out=%q status=%s\n' "$output" "$status"out=ls:\ cannot\ access\ \'/does-not-exist\':\ No\ such\ file\ or\ directory status=2The 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.
DEMO_SIDE=1
captured=$(DEMO_SIDE=2; printf '%s' "$DEMO_SIDE")
printf 'parent=%s captured=%s\n' "$DEMO_SIDE" "$captured"parent=1 captured=2The same rule applies to cd:
pwd_before=$(pwd)
inside=$(cd /tmp && pwd)
pwd_after=$(pwd)
printf 'before=%s\ninside=%s\nafter=%s\n' "$pwd_before" "$inside" "$pwd_after"before=/tmp/cmdsub-lab
inside=/tmp
after=/tmp/cmdsub-labcd /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:
output=$(command)
status=$?Apply it to a real command:
printf 'ok\n' > status-demo.txt
output=$(grep '^ok' status-demo.txt)
status=$?
printf 'match output=%q status=%s\n' "$output" "$status"match output=ok status=0When the pattern is missing, the substitution is empty but the status still reports failure:
output=$(grep '^missing' status-demo.txt 2>/dev/null)
status=$?
printf 'miss output=%q status=%s\n' "$output" "$status"miss output='' status=1Avoid local output=$(some_command) when you need some_command's exit status — local can mask it (ShellCheck SC2155). Declare and assign separately:
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.
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"read=file\ contents cat=file\ contentsGNU 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:
printf 'BASH_VERSINFO=%s.%s.%s\n' "${BASH_VERSINFO[0]}" "${BASH_VERSINFO[1]}" "${BASH_VERSINFO[2]}"Side effects persist with ${ command; }:
B53_SIDE=1
val=${ printf '%s' "$B53_SIDE"; B53_SIDE=2; }
printf 'B53_SIDE=%s val=%s\n' "$B53_SIDE" "$val"B53_SIDE=2 val=1${| command; } expands from REPLY and can preserve a trailing newline:
text=${| REPLY=$'line\n'; }
printf 'text=%q len=%s\n' "$text" "${#text}"text=$'line\n' len=5Use 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
- GNU Bash Manual — Command Substitution
- POSIX — Command Substitution
- GNU Bash 5.3 release announcement
- ShellCheck SC2006 — Use
$(...)instead of backticks - ShellCheck SC2155 — Declare and assign separately
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.

