问题出在SSH命令的重定向冲突上! 当前代码: while read hostname; do ...
생성일: 2025년 11월 26일
생성일: 2025년 11월 26일
问题出在SSH命令的重定向冲突上!
当前代码:
while read hostname; do
# SSH命令会消耗标准输入
local ip=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 "hostname'" 2>/dev/null </dev/null | awk -F'[()]' '/PING/{print 2}' | head -1)
if [[ -n "ip" ]]; then
echo "compute_hosts_file"
log_info "解析计算节点: ip"
else
log_warn "无法解析主机名: YARN_RM_HOST)"
fi
fi
done < "$compute_hosts_file.tmp"
我看不太懂这个解析, 标准输入输入到哪里去了? 也就是读取的文件,会暂时存到一个具体的地方,然后可能会被覆盖? 你能系统的讲讲吗
下面我会用非常系统、非常清晰的方式讲一下:
在 Linux 中,每一个进程默认有三个文件描述符:
| 文件描述符 | 名称 | 默认来源 |
|---|---|---|
| 0 | stdin(标准输入) | 键盘输入,或重定向进来的文件 |
| 1 | stdout(标准输出) | 终端 |
| 2 | stderr(错误输出) | 终端 |
当你写:
bashwhile read hostname; do ssh hostA "ping xxx" done < file.txt
其实等价于:
SSH 进程会试图读取标准输入,以便支持以下功能:
因此,ssh 默认会“接管”它自己的 stdin。
但 ssh 的 stdin 是继承父进程的 stdin,也就是 while read 的 stdin → file.txt!
⚠️ 这意味着:
ssh 会从 file.txt(你的主机名文件)中继续读取数据!
所以结果就是:
所以你的代码会只处理第一行,然后立刻结束。
完全不会。
关键在于:
while read 的 stdin 和 ssh 的 stdin 都指向同一个文件描述符(FD 0)
而不是复制文件内容或保存到某处
没有数据“暂存”。只是两个进程意外地“共用”了同一个输入源。
像这样:
textfile.txt --(FD 0)--> while read file.txt --(FD 0)--> ssh ← 问题就在这
ssh 不该使用 file.txt 的内容,但它默认继承了 stdin,所以一起用了。
这个写法:
bashssh hostA "ping xxx" </dev/null
意思是:
ssh 的 stdin 不再来自 file.txt,而是来自 /dev/null
于是:
textfile.txt → while read dev/null → ssh
现在 ssh 就不会误读 file.txt,也不会干扰 while read。
textfile.tmp | |----> read hostname | `----> ssh host "cmd" (意外地继续读取 file.tmp)
结果:ssh 把文件读完 → while 循环提前结束
textfile.tmp ------> read hostname /dev/null -----> ssh host "cmd"
结果:read 正常读文件,ssh 不干扰
| 现象 | 原因 |
|---|---|
| while 只执行一次 | ssh 抢占 stdin,把文件读空了 |
| ssh 为什么读 stdin | 它默认继承父进程的 stdin |
| 输入到哪里去了? | 没“去”哪里,只是被 ssh 消费了 |
| 如何解决? | 给 ssh 单独的 stdin:</dev/null |
bashwhile 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
<<< 或 printf '%s\n' 替代 while read?随时告诉我!
ssh 的 stdin 不再来自 file.txt,而是来自 /dev/null 我提供了 ssh 需要读取的命令,但是他还是把 file中的内容给读掉了是吧?
对,你抓住了关键点,只差一小步概念上的区分,我帮你把这个“最后一步”踩稳。
ssh 的 stdin 不再来自 file.txt,而是来自 /dev/null 我提供了 ssh 需要读取的命令,但是他还是把 file中的内容给读掉了是吧?
</dev/null 之前:
</dev/null 之后:
/dev/null,根本“看不到”那个文件。你现在纠结的是:
“我都已经在命令行里提供了
ping -c 1 '$hostname',ssh 还要读 stdin 干嘛?而且 stdin 还是那个 file?”
这就引出最重要的一点——👇
当你写:
bashssh "$YARN_RM_HOST" "ping -c 1 '$hostname'"
这里有两种“数据”在传给 ssh:
命令参数(arguments)
"ping -c 1 '$hostname'" 是 ssh 的参数标准输入(stdin)
file.tmp也就是说:
这两个通道互不影响,但 ssh 会自动把它的 stdin 内容转发给远端程序,即便你远端的 ping 完全不读 stdin。
在你最初的写法中:
bashwhile read hostname; do ssh "$YARN_RM_HOST" "ping -c 1 '$hostname'" 2>/dev/null | ... done < "$compute_hosts_file.tmp"
发生的事是:
while ... done < file.tmp → 整个循环的 stdin = file.tmpread hostname 先从 file.tmp 读一行,比如 host1ssh ...,此时:
ping 虽然不读 stdin,也没关系——反正本地这边已经把文件读完了read hostname 想读第二行时,发现:已经没东西了 → 返回 EOF → while 结束所以,你看到的现象就是:只处理第一行。
</dev/null 或 -n 后发生了什么变化?修正后的写法:
bashssh "$YARN_RM_HOST" "ping -c 1 '$hostname'" </dev/null
或者:
bashssh -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/nullread 一行一行消耗,循环能正常跑完“我提供了 ssh 需要读取的命令,但是它还是把 file 中的内容给读掉了,是吧?”
更精确地说:
"ping -c 1 '$hostname'" 是 ssh 的命令参数,不是 stdin。</dev/null 或 -n),不要让它继承 while 的那个文件描述符。如果你愿意,我们可以进一步用 strace 的角度来想象 ssh 是怎么 read(0, ...) 把文件内容读走的,或者我可以帮你写一个极小的 demo 脚本,让你在终端跑一跑就能直观看出“谁在读 stdin”。