使用()和$()执行一系列命令的区别

Myk*_*III 13 shell pipe shell-script command-substitution

我目前正在尝试制作一个脚本来创建字节,这些字节将作为输入传输到 netcat。

这是脚本的想法:

(perl -e "print \"$BYTES\x00\";

cat file;

perl -e "print \"More bytes\"x16 . \"\r\n\"";) | netcat ip port
Run Code Online (Sandbox Code Playgroud)

我尝试使用 subshel​​l 和命令替换(例如使用 $())来执行命令。但是我不明白为什么使用命令替换时脚本的输出是错误的。我怀疑在执行多个命令时,命令替换会错误地管道输出。有人可以向我解释为什么会这样吗?

编辑

这是使用命令替换的变体:

$(perl -e "print \"$BYTES\x00\";

cat file;

perl -e "print \"More bytes\"x16 . \"\r\n\"";) | netcat ip port
Run Code Online (Sandbox Code Playgroud)

Hal*_*ost 24

好的,让我们分解一下。一个子shell 在一个链中执行它的内容(即,它对它们进行分组)。这实际上具有直观的意义,因为只需用(). 但是,除了在执行时将子shell 的内容组合在一起之外,您仍然可以像使用单个命令一样使用子shell。也就是说,一个子shell仍然有一个stdinstdout并且stderr让你可以管的事情,并从子shell。

另一方面,命令替换与简单地将命令链接在一起不同。相反,命令替换的作用有点像变量访问,但具有函数调用。与命令不同,变量没有标准的文件描述符,因此您不能通过管道将任何东西传入或传出变量(一般来说),命令替换也是如此。

为了更清楚地说明这一点,下面是一组可能不清楚(但准确)的示例,以及一组我认为可能是更容易理解的示例。

假设该date -u命令提供以下内容:

Thu Jul  2 13:42:27 UTC 2015
Run Code Online (Sandbox Code Playgroud)

但是,我们想要操纵这个命令的输出。所以,让我们用管道把它变成类似的东西sed

user@host~> date -u | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015
Run Code Online (Sandbox Code Playgroud)

哇,那很有趣!以下内容完全等同于上述内容(除了您可以在有关 shell 的手册页中阅读的一些环境差异之外):

user@host~> (date -u) | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015
Run Code Online (Sandbox Code Playgroud)

这应该不足为奇,因为我们所做的只是 group date -u。但是,如果我们执行以下操作,我们会得到一些起初看起来有点奇怪的东西:

user@host~> $(date -u) | sed -e 's/ /    /g'
command not found: Thu
Run Code Online (Sandbox Code Playgroud)

这是因为$(date -u)等价于准确输入date -u输出的内容。所以上面的等价于下面的:

user@host~> Thu Jul  2 13:42:27 UTC 2015 | sed -e 's/ /    /g'
Run Code Online (Sandbox Code Playgroud)

当然,这会出错,因为Thu它不是命令(至少不是我所知道的);它当然不会通过管道传输任何内容stdout(因此sed永远不会获得任何输入)。

但是,因为我们知道命令替换就像变量一样,所以我们可以轻松解决这个问题,因为我们知道如何将变量的值通过管道传输到另一个命令中:

user@host~> echo $(date -u) | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015
Run Code Online (Sandbox Code Playgroud)

但是,与 bash 中的任何变量一样,您可能应该用"".

现在,对于可能更简单的示例;考虑以下:

user@host~> pwd
/home/hypothetical
user@host~> echo pwd
pwd
user@host~> echo "$(pwd)"
/home/hypothetical
user@host~> echo "$HOME"
/home/hypothetical
user@host~> echo (pwd)
error: your shell will tell you something weird that roughly means “Whoa! you tried to have me echo something that isn't text!”
user@host~> (pwd)
/home/hypothetical
Run Code Online (Sandbox Code Playgroud)

我不知道如何描述它比这更简单。命令替换就像变量访问一样工作,其中子外壳仍然像命令一样运行。


Arc*_*mar 7

子壳

(command)command在子shell中执行。如果您有多个命令,这很有用。

  • (ls) | wc将管道输出lsto wc,显然你可以写ls | wc.
  • (ls ; date) | wc将通过管道将ls和的输出date传送到wc。使用ls ; date | wc将导致仅date通过管道传输到wc.

代换

$(command)将执行command并替换为输出。例如

echo $(date)
Run Code Online (Sandbox Code Playgroud)

将替换$(date)Thu Jul 2 15:20:43 CEST 2015,这导致

echo Thu Jul  2 15:20:43 CEST 2015
Run Code Online (Sandbox Code Playgroud)

将两者放在一起

您可以将两者结合起来。

file=/hello/word
( printf "%s has %d bytes\n" "${file}" $(wc -c < "$file") ; date ) | netcat ...
Run Code Online (Sandbox Code Playgroud)

在这里,您可以使用"$file""${file}"

不要忘记引用文件名,在幻想世界中文件名是普通文件,而在现实世界中文件名通常包含换行符、空格、逗号、制表和括号。