bash-hackers-wiki/docs/syntax/expansion/cmdsubst.md

139 lines
4.4 KiB
Markdown
Raw Normal View History

2023-07-05 11:43:35 +02:00
# Command substitution
![](keywords>bash shell scripting expansion substitution text variable output execute stdout save result return value)
$( <COMMANDS> )
` <COMMANDS> `
The command substitution expands to the output of commands. These
commands are executed in a subshell, and their `stdout` data is what the
substitution syntax expands to.
All **trailing** newlines are removed (below is an example for a
workaround).
In later steps, **if not quoted**, the results undergo [word
splitting](../../syntax/expansion/wordsplit.md) and [pathname
expansion](../../syntax/expansion/globs.md). You have to remember that, because
2023-07-05 11:43:35 +02:00
the word splitting will also remove embedded newlines and other `IFS`
characters and break the results up into several words. Also you\'ll
probably get unexpected pathname matches. **If you need the literal
results, quote the command substitution!**
The second form `` `COMMAND` `` is more or less obsolete for Bash, since
it has some trouble with nesting (\"inner\" backticks need to be
escaped) and escaping characters. Use `$(COMMAND)`, it's also POSIX!
2023-07-05 11:43:35 +02:00
When you [call an explicit subshell](../../syntax/ccmd/grouping_subshell.md)
2023-07-05 11:43:35 +02:00
`(COMMAND)` inside the command substitution `$()`, then take care, this
way is **wrong**:
$((COMMAND))
Why? because it collides with the syntax for [arithmetic
expansion](../../syntax/expansion/arith.md). You need to separate the command
2023-07-05 11:43:35 +02:00
substitution from the inner `(COMMAND)`:
$( (COMMAND) )
## Specialities
When the inner command is only an input redirection, and nothing else,
for example
$( <FILE )
# or
` <FILE `
then Bash attempts to read the given file and act just if the given
command was `cat FILE`.
## A closer look at the two forms
In general you really should only use the form `$()`, it's
escaping-neutral, it's nestable, it's also POSIX. But take a look at
2023-07-05 11:43:35 +02:00
the following code snips to decide yourself which form you need under
specific circumstances:
**[Nesting]{.underline}**
Backtick form `` `...` `` is not directly nestable. You will have to
escape the \"inner\" backticks. Also, the deeper you go, the more escape
characters you need. Ugly.
echo `echo `ls`` # INCORRECT
echo `echo \`ls\`` # CORRECT
echo $(echo $(ls)) # CORRECT
**[Parsing]{.underline}**
All is based on the fact that the backquote-form is simple character
substitution, while every `$()`-construct opens an own, subsequent
parsing step. Everything inside `$()` is interpreted as if written
normal on a commandline. No special escaping of **nothing** is needed:
echo "$(echo "$(ls)")" # nested double-quotes - no problem
**[Constructs you should avoid]{.underline}**
It's not all shiny with `$()`, at least for my current Bash
2023-07-05 11:43:35 +02:00
(`3.1.17(1)-release`. :!: [**Update:** Fixed since `3.2-beta` together
with a misinterpretion of \'))\' being recognized as arithmetic
expansion \[by redduck666\]]{.underline}). This command seems to
incorrectly close the substitution step and echo prints \"ls\" and
\")\":
echo $(
# some comment ending with a )
ls
)
It seems that every closing \")\" confuses this construct. Also a (very
uncommon ;-)) construct like:
echo $(read VAR; case "$var" in foo) blah ;; esac) # spits out some error, when it sees the ";;"
# fixes it:
echo $(read VAR; case "$var" in (foo) blah ;; esac) # will work, but just let it be, please ;-)
**[Conclusion:]{.underline}**
In general, the `$()` should be the preferred method:
- it's clean syntax
- it's intuitive syntax
- it's more readable
- it's nestable
2023-07-05 11:43:35 +02:00
- its inner parsing is separate
## Examples
**To get the date:**
DATE="$(date)"
**To copy a file and get `cp` error output:**
COPY_OUTPUT="$(cp file.txt /some/where 2>&1)"
Attention: Here, you need to redirect `cp` `STDERR` to its `STDOUT`
target, because command substitution only catches `STDOUT`!
**Catch stdout and preserve trailing newlines:**
var=$(echo -n $'\n'); echo -n "$var"; # $var == ""
var=$(echo -n $'\n'; echo -n x); var="${var%x}"; echo -n "$var" # $var == "\n"
This adds \"x\" to the output, which prevents the trailing newlines of
the previous commands\' output from being deleted by \$().
By removing this \"x\" later on, we are left with the previous
commands\' output with its trailing newlines.
## See also
- Internal: [Introduction to expansion and
substitution](../../syntax/expansion/intro.md)
- Internal: [Obsolete and deprecated syntax](../../scripting/obsolete.md)