问题出在SSH命令的重定向冲突上! 当前代码: while read hostname; do ...

Criado em: 26 de novembro de 2025

Respondido usando GPT-5.1 por Chat01

Pergunta

问题出在SSH命令的重定向冲突上!

当前代码:

while read hostname; do
# SSH命令会消耗标准输入
local ip=(ssh"(ssh "YARN_RM_HOST" "ping -c 1 'hostname'" 2>/dev/null | awk -F'[()]' '/PING/{print 2}' | head -1)
# ...后续处理
done < "$compute_hosts_file.tmp"
问题分析:

while read hostname 从 $compute_hosts_file.tmp 读取输入
但是ssh命令也会从标准输入读取数据
当第一个SSH命令执行时,它会消耗掉剩余的所有输入
导致read hostname在第二次循环时读到EOF,循环结束
解决方案
方法一:使用重定向避免SSH读取标准输入

while read hostname; do
if [[ -n "hostname" ]]; then # 将ssh的标准输入重定向到/dev/null local ip=(ssh "YARNRMHOST""pingc1YARN_RM_HOST" "ping -c 1 'hostname'" 2>/dev/null </dev/null | awk -F'[()]' '/PING/{print 2}' | head -1) if [[ -n "ip" ]]; then
echo "ip">>"ip" >> "compute_hosts_file"
log_info "解析计算节点: hostname>hostname -> ip"
else
log_warn "无法解析主机名: hostname(YARN主机:hostname (在YARN主机: YARN_RM_HOST)"
fi
fi
done < "$compute_hosts_file.tmp"

我看不太懂这个解析, 标准输入输入到哪里去了? 也就是读取的文件,会暂时存到一个具体的地方,然后可能会被覆盖? 你能系统的讲讲吗

Resposta

下面我会用非常系统、非常清晰的方式讲一下:


为什么 SSH 会“吃掉” while read 的输入?标准输入到底是什么?

📌 1. 首先理解:标准输入(stdin)是什么?

在 Linux 中,每一个进程默认有三个文件描述符:

文件描述符名称默认来源
0stdin(标准输入)键盘输入,或重定向进来的文件
1stdout(标准输出)终端
2stderr(错误输出)终端

当你写:

bash
while read hostname; do ssh hostA "ping xxx" done < file.txt

其实等价于:

  • 整个 while 循环的 stdin = file.txt
  • read hostname 就是从 stdin(也就是 file.txt)读数据

📌 2. 关键点:ssh 默认也会从 stdin 读取数据!

SSH 进程会试图读取标准输入,以便支持以下功能:

  • 远程命令可以读取本地输入
  • 可以执行远程交互程序
  • 可以传输本地数据给远程命令

因此,ssh 默认会“接管”它自己的 stdin。

但 ssh 的 stdin 是继承父进程的 stdin,也就是 while read 的 stdin → file.txt!

⚠️ 这意味着:

ssh 会从 file.txt(你的主机名文件)中继续读取数据!

所以结果就是:

  • 第一次 read hostname 读取了 file.txt 的第一行
  • ssh 启动后把 file.txt 后面的内容全都读走了
  • while 循环下一次调用 read hostname → 发现文件已经 EOF → 循环提前结束

所以你的代码会只处理第一行,然后立刻结束。


📌 3. 那么 文件会不会被覆盖?会不会“暂时存在哪”?

完全不会。

关键在于:

while read 的 stdin 和 ssh 的 stdin 都指向同一个文件描述符(FD 0)
而不是复制文件内容或保存到某处

没有数据“暂存”。只是两个进程意外地“共用”了同一个输入源。

像这样:

text
file.txt --(FD 0)--> while read file.txt --(FD 0)--> ssh ← 问题就在这

ssh 不该使用 file.txt 的内容,但它默认继承了 stdin,所以一起用了。


📌 4. 为什么加 </dev/null 能解决?

这个写法:

bash
ssh hostA "ping xxx" </dev/null

意思是:

ssh 的 stdin 不再来自 file.txt,而是来自 /dev/null

于是:

text
file.txt → while read dev/null → ssh

现在 ssh 就不会误读 file.txt,也不会干扰 while read。


📌 5. 图示帮助理解

❌ 错误情况(stdin 被 ssh 抢走)

text
file.tmp | |----> read hostname | `----> ssh host "cmd" (意外地继续读取 file.tmp)

结果:ssh 把文件读完 → while 循环提前结束


✔ 正确情况(ssh 的 stdin 被重定向)

text
file.tmp ------> read hostname /dev/null -----> ssh host "cmd"

结果:read 正常读文件,ssh 不干扰


📌 6. 关键结论总结

现象原因
while 只执行一次ssh 抢占 stdin,把文件读空了
ssh 为什么读 stdin它默认继承父进程的 stdin
输入到哪里去了?没“去”哪里,只是被 ssh 消费了
如何解决?给 ssh 单独的 stdin:</dev/null

📌 7. 推荐的写法(最干净)

bash
while read -r hostname; do [[ -z $hostname ]] && continue ip=$(ssh -n "$YARN_RM_HOST" "ping -c 1 '$hostname'" \ 2>/dev/null | awk -F'[()]' '/PING/{print $2}' | head -1) done < "$compute_hosts_file.tmp"

ssh -n 的作用:

告诉 ssh 不要从 stdin 读取数据
即自动相当于重定向 stdin → /dev/null


如你愿意,我可以继续讲:

  • 为什么 ssh 默认读取 stdin 的技术细节?
  • 为什么 while read 和管道时又完全是另一套机制?
  • 为什么有些人喜欢 <<<printf '%s\n' 替代 while read?
  • 或者我可以画图来解释 file descriptor 的继承机制?

随时告诉我!

Pergunta

ssh 的 stdin 不再来自 file.txt,而是来自 /dev/null 我提供了 ssh 需要读取的命令,但是他还是把 file中的内容给读掉了是吧?

Pensamento

Clarifying SSH behavior with stdin

When running a remote command, SSH typically reads from stdin if not using -n. This allows the SSH client to forward the file and send it remotely. If stdin is piped or a regular file, SSH reads until EOF

Thought for 13s

Resposta

对,你抓住了关键点,只差一小步概念上的区分,我帮你把这个“最后一步”踩稳。


1. 你说的这句,其实分两种情况

ssh 的 stdin 不再来自 file.txt,而是来自 /dev/null 我提供了 ssh 需要读取的命令,但是他还是把 file中的内容给读掉了是吧?

  • 在没加 </dev/null 之前
    • 是的,ssh 会把 file 中剩余的内容读掉
  • 在加了 </dev/null 之后
    • ❌ 它就不会再读 file 了,因为它的 stdin 已经被换成 /dev/null,根本“看不到”那个文件。

你现在纠结的是:

“我都已经在命令行里提供了 ping -c 1 '$hostname',ssh 还要读 stdin 干嘛?而且 stdin 还是那个 file?”

这就引出最重要的一点——👇


2. 命令参数 ≠ 标准输入(stdin)

当你写:

bash
ssh "$YARN_RM_HOST" "ping -c 1 '$hostname'"

这里有两种“数据”在传给 ssh:

  1. 命令参数(arguments)

    • "ping -c 1 '$hostname'"ssh 的参数
    • ssh 会把这个参数通过 SSH 协议告诉远端,让远端 shell 去执行这条命令
    • 这条命令 不是从 stdin 读到的,是单独的“控制信道/控制数据”
  2. 标准输入(stdin)

    • ssh 这个进程本身仍然有一个 stdin(文件描述符 0)
    • 默认情况下,这个 stdin 是从它的父进程继承来的
    • 在你的 while 代码里,父进程(shell)把 stdin 重定向成了 file.tmp
      所以 ssh 的 stdin = file.tmp

也就是说:

  • 命令文本:由 ssh 参数传过去
  • stdin 的数据:是另一个通道,默认从当前进程的 stdin 过来(在你这里就是 file)

这两个通道互不影响,但 ssh 会自动把它的 stdin 内容转发给远端程序,即便你远端的 ping 完全不读 stdin。


3. 那 ssh 是怎么“吃掉文件”的?

在你最初的写法中:

bash
while read hostname; do ssh "$YARN_RM_HOST" "ping -c 1 '$hostname'" 2>/dev/null | ... done < "$compute_hosts_file.tmp"

发生的事是:

  1. while ... done < file.tmp → 整个循环的 stdin = file.tmp
  2. 第一次循环:
    • read hostname 先从 file.tmp 读一行,比如 host1
    • 然后执行 ssh ...,此时:
      • ssh 的 stdin 也是 同一个 file.tmp(从当前文件光标位置往后)
  3. ssh 的行为:
    • 它认为“我要把我的 stdin 转发给远端命令”
    • 所以它开始从 stdin(file.tmp)持续读,直到 EOF 为止,然后通过网络发给远端
    • 远端的 ping 虽然不读 stdin,也没关系——反正本地这边已经把文件读完了
  4. 当 ssh 退出后,文件指针已经到了 EOF
    下一轮 read hostname 想读第二行时,发现:已经没东西了 → 返回 EOF → while 结束

所以,你看到的现象就是:只处理第一行


4. 加上 </dev/null-n 后发生了什么变化?

修正后的写法:

bash
ssh "$YARN_RM_HOST" "ping -c 1 '$hostname'" </dev/null

或者:

bash
ssh -n "$YARN_RM_HOST" "ping -c 1 '$hostname'"

这两种都是把 ssh 的 stdin 从“file.tmp”改成了别的:

  • </dev/null:stdin 变成 /dev/null
  • -n:等价于把 stdin 重定向到 /dev/null(对大多数 ssh 实现)

所以现在:

  • while read 仍然从 file.tmp
  • ssh 再也不会碰 file.tmp 了,它的 stdin 是 /dev/null
  • 因此文件只会被 read 一行一行消耗,循环能正常跑完

5. 一句话总结你问的那个点

“我提供了 ssh 需要读取的命令,但是它还是把 file 中的内容给读掉了,是吧?”

更精确地说:

  • 你提供的 "ping -c 1 '$hostname'"ssh 的命令参数,不是 stdin
  • ssh 默认会额外把自己的 stdin(在你的场景里是 file.tmp)读完并转发给远端,所以 file 被“吃干净了”。
  • 解决方法就是:给 ssh 单独的 stdin</dev/null-n),不要让它继承 while 的那个文件描述符。

如果你愿意,我们可以进一步用 strace 的角度来想象 ssh 是怎么 read(0, ...) 把文件内容读走的,或者我可以帮你写一个极小的 demo 脚本,让你在终端跑一跑就能直观看出“谁在读 stdin”。

Compartilhar esta Q&A