`$( ... )` 和 `( ... )` 有什么区别?

What is the difference between `$( ... )` and `( ... )`?

$( ... )( ... ) 以及 . (source) 之间有什么区别?


在开始编写 shell 脚本时,我了解到 $( ... ) 是合适的,如果我想使用命令的输出,例如对于一个变量:

size=$( wc -c < /path/to/file )

然后我发现如果我想执行命令(在子shell中),我可以使用( ... ),等待它退出并使用它的输出文件,喜欢:

( echo -e "a lot of data, if everything worked... ;-)" > /path/to/file )

有趣的是,它也适用于 $( ... ),但给出了某种神秘的错误消息 scriptname: line 1: : Command not found.:

$( echo -e "a lot of data, if everything worked... ;-)" > /path/to/file )

为什么 $( ... ) 不能完美地适用于这两个用例?考虑到错误消息,我猜原因是这样的:$( ... ) “在 shell 处甚至强制输出一个空输出”,尽管有 none,而 ( ... ) 只是执行命令,不关心输出。是吗?


我知道还有. (别名source)在当前的shell中执行一个命令,好像和$( ... )更像, 关于功能。

或者 . ( ... ) 之间是否存在更多差异,而不仅仅是在当前或我不知道的子 shell 中执行命令?

$( cmd ) 执行 cmd 然后尝试执行其输出。所以

$( echo -e "a lot of data, if everything worked... ;-)" > /path/to/file )

执行将数据写入 /path/to/file 且不产生任何输出的命令。 shell 获取该输出(空字符串)并尝试执行它,生成您看到的错误消息。

只有 运行 命令,无论是否在子 shell 中,在执行这些命令后不涉及与 shell 本身的任何交互。

$ echo echo poo
echo poo

相比之下,命令替换在完成后由 shell 评估:

$ $(echo echo poo)
poo

$ var=$(echo echo poo)

$ echo "$var"
echo poo

$ $(echo "$var")   # expands to the first line (but is unnecessarily redundant)
poo

$ $var             # more succinct version of the previous line
poo

当然,如果您重定向命令替换的输出,shell 最终会评估一个空字符串。

. 命令(或者,在 Bash 和其他一些 non-POSIX shell 中,它的同义词 source)计算一系列命令一份文件。这在某种意义上隐约类似于命令替换,但后者允许您捕获和操纵评估结果(在某种意义上,shell 的整个 purpose是计算表达式)。

命令替换实际上可以用于计算任何地方的表达式,而 . 或 subshell 只能在 shell 接受命令的地方使用。看看这些体操:

$ $(echo e)$(echo c)$(echo h)$(echo o) "poo"
poo

$ echo p$(printf x | wc -l | tr -d ' ' | tr '1' 'o')o
poo

$ ech$(echo o poo)
poo

正如您发现的那样,当您希望一组命令共享一些文件描述符时,subshell 会很有用;

( printf '%s\n' 'Subject: hello' \
    'MIME-version: 1.0' \
    'Content-type: application/octet-stream' \
    'Content-transfer-encoding: base64' \
    ''
  base64 binaryfile ) |
sendmail -oi you@example.net

或者如果你想限制一些环境变化的范围;

for subdir in */; do
  ( cd "$subdir"
    condiments=$(cat spice.txt)
    export condiments
    make -s dinner )
# back in parent dir
# $condiments is back to its old value
# probably unset, and not exported
done