Bash脚本中While循环仅执行一次的问题排查
问题:Bash循环处理服务器列表仅执行一次就停止
我编写了如下Bash脚本用于批量部署软件包:
#!/bin/bash USERNAME=xenobot SSH_KEY_LOCATION="~/.ssh/id_rsa" FILE="server.info" while read -r server_ip package_type; do if [[ $package_type == "deb" ]]; then echo "For $server_ip package type is $package_type" DPKG_FILE_PATH=$(ls ./src/dpkg/dpkg-*) DPKG_FILE=$(basename $DPKG_FILE_PATH) echo "$DPKG_FILE located at $DPKG_FILE_PATH will be transfered via SSH to server" scp ./src/dpkg/${DPKG_FILE} $USERNAME@$server_ip:/tmp ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l ${USERNAME} ${server_ip} "cd /tmp; sudo dpkg -i *.deb" elif [[ $package_type == "rpm" ]]; then echo "For $server_ip package type is $package_type" RPM_FILE_PATH=$(ls ./src/rpm/rpm-*) RPM_FILE=$(basename $RPM_FILE_PATH) echo "$RPM_FILE located at $RPM_FILE_PATH will be transfered via SSH to server" scp -o 'StrictHostKeyChecking no' ./src/rpm/${RPM_FILE} $USERNAME@$server_ip:/tmp echo "$RPM_FILE has been successfuly transfered to server!" ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l ${USERNAME} ${server_ip} "cd /tmp; sudo rpm -ivh --force $RPM_FILE" fi done <"$FILE"
脚本读取server.info文件内容(内容为213.136.80.123 rpm 213.136.91.102 rpm)执行部署,但运行时循环仅处理第一条记录就停止,未执行第二条。
而仅保留打印逻辑的测试脚本:
#!/bin/bash USERNAME=xenobot SSH_KEY_LOCATION="~/.ssh/id_rsa" FILE="server.info" while read -r server_ip package_type; do if [[ $package_type == "deb" ]]; then echo "For $server_ip package type is $package_type" elif [[ $package_type == "rpm" ]]; then echo "For $server_ip package type is $package_type" fi done <"$FILE"
可正常输出两条记录,需排查循环仅执行一次的原因。
原因分析
问题出在ssh和scp命令上:这两个命令默认会读取标准输入(stdin),而你的while循环是通过重定向从$FILE读取输入(此时stdin指向$FILE)。当第一次循环中执行ssh或scp时,它们会把$FILE中剩余的所有内容全部读走,导致下一次read命令没有内容可读取,循环直接终止。
解决方案
有两种可靠的解决方式:
方式一:给ssh/scp添加-n参数
-n参数会让ssh/scp不读取标准输入,避免它们消耗$FILE中剩余的内容。修改脚本中的scp和ssh命令:
处理deb包的部分:
scp -n ./src/dpkg/${DPKG_FILE} $USERNAME@$server_ip:/tmp ssh -n -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l ${USERNAME} ${server_ip} "cd /tmp; sudo dpkg -i *.deb"
处理rpm包的部分:
scp -n -o 'StrictHostKeyChecking no' ./src/rpm/${RPM_FILE} $USERNAME@$server_ip:/tmp ssh -n -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l ${USERNAME} ${server_ip} "cd /tmp; sudo rpm -ivh --force $RPM_FILE"
方式二:使用独立的文件描述符读取输入
将$FILE的输入重定向到一个非标准的文件描述符(比如3),让read从这个描述符读取内容,这样ssh/scp就不会干扰循环的输入源。修改循环的写法:
while read -r server_ip package_type <&3; do # 原有循环内的所有逻辑保持不变 done 3<"$FILE"
这种方式彻底隔离了循环的输入和ssh/scp的输入,避免冲突。
内容的提问来源于stack exchange,提问作者Ruslan Pylypiuk
相关产品推荐
相关产品推荐

