echo $(command) 得到与命令输出不同的结果

echo $(command) gets a different result with the output of the command

我使用的Bash命令:

  1. $ ssh user@myserver.com ps -aux|grep -v \"grep\"|grep "/srv/adih/server/app.js"|awk '{print }'

    6373
    
  2. $ ssh user@myserver.com echo $(ps -aux|grep -v \"grep\"|grep "/srv/adih/server/app.js"|awk '{print }')

    8630
    

第一个结果是正确的,第二个会改变回显时间我执行它。但是不知道为什么不一样


我在做什么?

我的工作站资源非常有限,所以我使用远程机器 运行 我的 Node.js 应用程序。我 运行 它在调试模式下使用 ssh user@remotebox.com "cd /application && grunt serve"。当我命令 Ctrl + C 时,g运行t 任务停止了,但应用程序仍然是 运行宁在调试模式。我就是想干掉它,需要先获取PID

命令替换由本地 shell 在 ssh 运行 之前执行。

如果您的本地系统名称是 here 而远程系统名称是 there

ssh there uname -n

将打印 there

ssh there echo $(uname -n)  # should have proper quoting, too

会在本地运行 uname -n然后将扩展后的命令行echo here发送到there执行

另外,echo $(command) 是一个 useless use of echo,除非您特别要求 shell 在打印前对 command 的输出执行通配符扩展和空格标记化它。

此外,grep x | awk { y } 是一个 useless use of grep;它可以而且可能应该重构为 awk '/x/ { y }'——当然,这里你正在重新发明 pidof,所以最好只使用它。

 ssh user@myserver.com pidof /srv/adih/server/app.js

如果你想在本地捕获打印的 PID,语法是

pid=$(ssh user@myserver.com pidof /srv/adih/server/app.js)

当然,如果你只需要杀掉它,那就

ssh user@myserver.com pkill /srv/adih/server/app.js

简短回答:$(ps ... ) 命令替换正在本地计算机上 运行,然后它的输出(与 echo 命令一起)发送到远程计算机。本质上,它是 运行ning ssh user@myserver.com echo 8630.

您的第一个命令也可能没有按照您的预期进行;管道在本地计算机上进行解释,因此它是 运行ning ssh user@myserver.com ps -aux,将输出通过管道传输到本地计算机上的 grep ,通过管道将其传输到另一个 grep 在本地计算机上,等等。我猜你想把整个东西 运行 在远程计算机上,这样结果就可以在远程计算机上用来终止进程。

长答案:在 shell 中解析和执行事物的顺序有点混乱;混合使用 ssh 命令,事情变得更加复杂。基本上,发生的事情是本地 shell 解析命令行,包括将其拆分为单独的命令(由管道分隔,; 等),并扩展 $(command)$variable 替换(除非它们在单引号中)。然后它会删除引号和转义符(它们已经完成了它们的工作)并将结果作为参数传递给各种命令(例如 ssh)。 ssh 接受它的参数,将所有看起来像远程命令的部分的参数与它们之间的空格粘贴在一起,并将它们发送到远程计算机 上的 shell 执行此过程再来一次.

这意味着引用 and/or 转义像 $| 这样的东西是必要的,如果你想让它们在远程 shell 上 parsed/acted而不是本地 shell。并且引号不嵌套,因此在整个事物周围放置引号可能无法按您预期的方式工作(例如,如果您不小心,该 awk 命令中的 </code> 可能会在本地计算机上扩展,即使它看起来像是在单引号中)。</p> <p>当事情变得像这样混乱时,最简单的方法有时是将远程命令作为此处文档而不是作为参数传递给 <code>ssh 命令。但是您希望在 here-document 定界符周围加上引号,以防止本地 shell 进行各种 $ 扩展。像这样:

ssh user@myserver.com <<'EOF'
echo $(ps -aux|grep -v "grep"|grep "/srv/adih/server/app.js"|awk '{print }')
EOF

注意:小心缩进远程命令,因为文本将按字面意思发送到远程计算机。如果您使用制表符缩进,您可以使用 <<- 作为此处文档的分隔符(例如 <<-'EOF'),它会删除前导制表符。

编辑:正如@tripleee 指出的那样,不需要多个 grep,因为 awk 本身可以完成整个工作。也没有必要从结果中排除搜索命令 (grep -v grep),因为模式中的“/”字符需要转义,这意味着它不会匹配自身。因此您可以将管道简化为:

ps -aux | awk '/\/srv\/adih\/server\/app.js/ {print }'

现在,我一直假设实际目标是 kill 相关的 pid,而 echo 只是为了测试。如果是这样的话,实际的命令最终是:

ssh user@myserver.com <<'EOF'
kill $(ps -aux | awk '/\/srv\/adih\/server\/app.js/ {print }')
EOF

如果那不对,那么整个 echo $( ) 最好完全跳过。没有理由捕获管道的输出然后echo它,只是运行它并让它直接输出。

如果 pkill(或类似的东西)可用,使用它会简单得多。