Command substitution captures a command’s standard output and inserts the resulting text into a surrounding expression. The familiar $(command) form is useful for a timestamp, a computed label, or another small textual value. It is not a transparent channel for arbitrary bytes or a reliable way to transport a list of filenames. Its newline handling, execution environment, and exit status are part of the interface you need to understand.

Capture text, then decide how to use it

For the standard substitution form discussed here, Bash executes the enclosed commands in a subshell environment. It captures standard output and removes all trailing newline characters. Newlines inside the output remain. Double quotes prevent later word splitting and filename expansion, but they do not undo the removal of trailing newlines. If the exact final bytes matter, use a file or a stream instead of a shell variable.

A variable assignment such as result=$(producer) stores the resulting text without splitting it into arguments. When using the value later, write "$result" to preserve its boundary. Similarly, consumer "$(producer)" supplies a single argument, even when the captured result is empty. Leaving the substitution unquoted can create several arguments or no arguments, and stars in the output may match filenames in the working directory.

Standard error is separate from standard output unless you explicitly redirect it. A diagnostic can remain visible on the terminal while the captured value contains only the result. Redirecting both streams together may contaminate a machine-readable value with warning text. Design the producer’s output contract before deciding whether those streams belong together; suppressing every diagnostic can hide the reason a value is missing.

When it breaks

The subshell has its own variable changes and working directory. Assigning a variable or running cd inside the substitution does not update the caller’s corresponding state. Filesystem and network side effects still happen, however: creating a file inside the substitution creates a real file. Process isolation does not make an operation reversible or remove its external effects.

Successful capture also says nothing about whether the producer succeeded. An assignment-only command gets the status of its last command substitution, so if result=$(producer); then can check the result. In contrast, the status of printf '%s' "$(producer)" normally describes printf. A producer can emit partial output and then fail; retain its status immediately and decide whether that output is usable. A later logging command overwrites the value represented by $?.

Avoid relying on an implicit failure somewhere inside a multi-command substitution. Its final command can return success even after an earlier command failed. Explicitly test important operations in the producer and return the intended status. Likewise, in a function, separate a local declaration from the capturing assignment when you need to inspect the producer’s status; a declaration builtin can mask that status.

Substitution also collects output before the surrounding command runs, so it is unsuitable for unbounded output or interactive streaming. Do not use an unquoted substitution over find output to construct an arbitrary pathname list: whitespace and wildcard characters lose their original meaning. Pass filenames through an interface that retains boundaries, such as find’s exec facility, or use a deliberately specified record protocol.

Check it quickly

Run this complete block with Bash. It uses only shell builtins and asserts the output and status independently. No files or services need to be prepared. The producer with status seven is an intentional failure, and the test must still reach the final PASS line.

bash --noprofile --norc <<'BASH'
set -e
outer=parent
captured=$(outer=child; printf 'first\nsecond\n\n')
[[ $outer == parent ]]
[[ $captured == $'first\nsecond' ]]
printf 'captured <%q>\n' "$captured"
if partial=$(printf 'incomplete'; exit 7); then
  printf 'unexpected producer success\n' >&2
  exit 1
else
  status=$?
fi
[[ $status -eq 7 && $partial == incomplete ]]
printf 'producer status %s; partial <%s>\n' "$status" "$partial"
printf '%s' "$(exit 9)"
[[ $? -eq 0 ]]
IFS=$' \t\n'
set -- "$(printf 'two words\n')"
[[ $# -eq 1 && $1 == 'two words' ]]
set -- "$(printf '\n\n')"
[[ $# -eq 1 && $1 == '' ]]
printf 'PASS: capture, subshell scope, failure status, empty result\n'
BASH

The escaped captured value should contain one internal newline and no trailing newline. The next line reports status seven alongside the partial text. The deliberately successful outer printf demonstrates why checking only the consumer’s status misses producer failure. These checks establish the behavior of the small synchronous producers shown here. A real producer still needs its own timeout, output-format validation, and explicit error contract where those requirements apply.

Sources