Bash while 循环在调用 subshel​​l 时在第一次迭代后停止

Bash while loop stops after first iteration when subshell is called

这个设计的 bash 脚本演示了这个问题。

#!/bin/bash
while read -r node ; do
    echo checking $node for Agent;
       PID=$(ssh $node ""ps -edf | grep [j]ava | awk '{print }'"")
       echo $PID got to here.
done < ~/agents_master.list

agents_master.list 每行包含 1 个服务器:

server1
server2
server3

只输出以下内容:

checking server1 for Agent
Authorized use only
25176 got to here

服务器 2 和 3 甚至没有通过行 echo checking $node...

回显到屏幕上

如果我注释掉行 PID=$(.... 那么 while 会正确地完成整个 agents_master.list 文件...

checking server1 for Agent
got to here
checking server2 for Agent
got to here
checking server3 for Agent
got to here

从我所做的谷歌搜索来看,这听起来像是与 $(...) 创建的子 shell 有关,但我不明白为什么它会导致循环在第一台服务器停止,server1.

是的,可以重写此代码,但我很想了解 bash 的这种行为以及为什么将来会发生这种情况。

问题 - 问题之一 - ssh 正在将标准输入转发到远程服务器。碰巧,您在远程服务器上 运行ning 的命令(ps -edf,见下文)不使用其标准输入,但 ssh 仍会转发它读取的内容,以防万一。结果,read 没有任何内容可供读取,因此循环结束。

为避免这种情况,请使用 ssh -n(或自己将输入重定向到 /dev/null,这就是 -n 选项的作用)。

还有一些其他问题实际上并未干扰您的脚本执行。

首先,我不知道你为什么在

中使用""
ssh $node ""ps -edf | grep [j]ava | awk '{print }'""

"" "expands" 为空字符串,所以上面的内容实际上等同于

ssh $node ps -edf | grep [j]ava | awk '{print }'

这意味着 grepawk 命令正在本地主机上 运行; ps 命令的输出由 ssh 转发回本地主机。这不会改变任何东西,尽管它确实使 [j]ava 中的括号变得多余,因为 grep 不会出现在进程列表中,因为它不是 运行ning执行 ps 的主机。事实上,括号是多余的是件好事,因为如果当前工作目录中恰好有一个名为 java 的文件,它们可能不会出现在命令中。你真的应该引用那个论点。

我假设您的意图是 运行 远程计算机上的整个管道,在这种情况下您可能已经尝试过:

ssh $node "ps -edf | grep [j]ava | awk '{print }'"

发现没有用。它不会起作用,因为 awk 命令中的 </code> 将扩展为当前 shell 中的任何 <code></code> <em>not</em> 受内部单引号保护。就 bash 而言, <code> 只是双引号字符串的一部分。 (而且它还会将参数的问题转移到 grep 没有被引用到远程主机,所以如果远程主机的主目录中有一个名为 java 的文件,你就会遇到问题.

所以你真正想要的是

ssh -n $node 'ps -edf | grep "[j]ava" | awk "{print $2}"'

最后,不要使用 PID 作为 shell 变量的名称。所有大写的变量名通常是保留的,并且危险地接近 BASHPIDPPID,它们是特定的 bash 变量。你自己的 shell 变量应该有小写的名字,就像在任何其他编程语言中一样。