背景
我已经创建了一个脚本来:
- 一次读取一个 IP 列表
- 将配置文件从本地主机复制到远程主机
- 重启远程主机
- 关闭当前 ssh 会话
脚本内容:
#!/bin/bash
SSHPASS="OMITTED"
FILE_TO_COPY=/opt/someConfigFile.conf
TARGET_FOLDER=/opt/
echo "Reading ip list..."
cat $1 | while read ip
do
echo "Copying file to $ip"
sshpass -p $SSHPASS scp -o StrictHostKeyChecking=no -o ServerAliveInterval=3 $FILE_TO_COPY root@$ip:$TARGET_FOLDER
echo "Sending reboot command to $ip"
sshpass -p $SSHPASS ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=3 'nohup reboot > /dev/null & exit'
echo "Done for $ip"
done
echo "Done for all"
我的脚本从文本文件中读取条目,其中每个条目由新行分隔,例如:
192.168.XXX.XX1
192.168.XXX.XX2
192.168.XXX.XX3
192.168.XXX.XX4
当我运行此脚本时,./ConfigSender.sh /host_list.txt
我可以看到以下输出:
$> ./ConfigSender.sh /host_list.txt
Rading ip list...
Copying file to 192.168.XXX.XX1
Sending reboot command to 192.168.XXX.XX1
Done for 192.168.XXX.XX1
Done for all
$>
我期望看到文件中所有条目的输出。我怀疑里面的命令以while
某种方式破坏了执行。所以我编辑了脚本,只将读取的 ip 值打印到输出中:
#...
echo "Reading ip list..."
cat $1 | while read ip
do
echo "Copying file to $ip"
done
echo "Done for all"
这是输出:
$> ./ConfigSender.sh /host_list.txt
Rading ip list...
Copying file to 192.168.XXX.XX1
Copying file to 192.168.XXX.XX2
Copying file to 192.168.XXX.XX3
Copying file to 192.168.XXX.XX4
Done for all
$>
问题
很明显,原始命令中的命令while
导致了这种行为。while 循环有什么问题?我在这里遗漏了什么?
编辑
@harrymc 和 @Kamil Maciorowski 的回答都解决了我的问题,非常感谢。我决定接受 @harrymc 的回答,因为它更具描述性。
答案1
sshpass
通过操纵stdin
来欺骗用户,ssh
使其认为它正在从交互用户那里获取密码。当您使用... | while
样式循环时,循环将迭代来自的每一行stdin
,sshpass
在第一次调用后清除。这就是为什么只有第一行被执行的原因。
有几种可能的解决方案:
- 将 sshpass 标准输入重定向到
/dev/null
- 将整个循环体包裹在括号中以隔离 stdin(
{}
) - 在循环之前分配给一个数组,这样你就不会使用 stdin
readarray a < file
for ip in "${a[@]}"; do
- 循环遍历除 stdin 之外的文件描述符
while read -u 5 -r ip; do
...
done 5<file
答案2
我做了一些测试。我认为你的台词就像
sshpass …
读stdin
,他们“吃”了 提供的额外 IP cat
。你可以调查为什么它发生了,但知道它就足够了做发生。
解决方案是给他们一些其他的输入:
</dev/null sshpass …
或者你可以像这样重建你的循环:
for ip in `cat $1`
do
…
done
答案3
不确定它是否能解决问题,但您没有在 ssh reboot 命令行中提供用户和主机名。
sshpass -p $SSHPASS ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=3 root@$ip 'nohup reboot > /dev/null & exit'