当前位置:首页 > 百科

shell脚本批量执行命令

凌晨三点,本批服务器上的量执令日志文件把磁盘撑爆了。我登上跳板机,行命看着十几台机器上同样的本批错误日志,手动一台台登录、量执令grep、行命删除,本批干到天亮。量执令那次之后我写了个脚本,行命循环遍历IP列表,本批一条ssh命令批量执行。量执令现在再遇到这种破事,行命两分钟搞定。本批

批量执行命令的量执令核心就一句话:把命令通过循环或并行工具分发到多台机器上。最常见的行命做法是for循环加ssh。比如你有一个hosts.txt,每行一个IP:

#!/bin/bash for ip in $(cat hosts.txt); do echo "=== $ip ===" ssh user@$ip "df -h | grep /data" done

这个脚本顺序执行,一台接一台。十台以内还行,上百台就等到天荒地老。而且如果某台机器网络卡住,整个循环就堵在那里。我吃过这个亏——有台老机器的SSH端口响应极慢,脚本跑了四十分钟还没轮到最后几台。

解决办法是用&把每次ssh丢到后台,或者用parallel、pssh这类工具。parallel我用得最多,一行命令搞定并发:

parallel -j 10 ssh user@{ } "systemctl restart nginx" :::: hosts.txt

-j 10表示同时跑10个任务。这个数字别贪大,SSH连接本身有开销,并发太高反而拖慢整体速度,还容易触发目标机器的连接数限制。我一般设成机器数量的1/3到1/2。

有个细节容易被忽略:命令里的引号。ssh user@$ip "echo $HOSTNAME"里的$HOSTNAME会在本地展开,而不是在远程机器上。想要远程执行时再展开,得用单引号或者转义。这个坑我踩过不止一次,调试半天才发现变量在本地就被替换了。

还有一种场景是命令特别长或者逻辑复杂,塞进一行ssh里可读性极差。我的做法是把脚本先scp到目标机器,再远程执行:

for ip in $(cat hosts.txt); do scp deploy.sh user@$ip:/tmp/ ssh user@$ip "bash /tmp/deploy.sh" done

这样脚本在本地可以正常调试,传上去直接跑。缺点是每台机器都要传一次,如果脚本本身很大,网络传输就是瓶颈。折中方案是先把脚本放到内网的某台文件服务器上,远程机器用wget或curl拉取。

批量执行最怕的是“部分成功”。十台机器里八台执行成功,两台失败,你得知道是哪两台。所以脚本里一定要记录输出和退出码:

for ip in $(cat hosts.txt); do if ssh user@$ip "command" >> /tmp/batch.log 2>&1; then echo "$ip OK" >> /tmp/batch_result.log else echo "$ip FAILED" >> /tmp/batch_result.log fi done

别小看这个日志,出问题的时候它就是你的救命稻草。我习惯把每台机器的输出单独存一个文件,命名带上IP和时间戳,事后排查一目了然。

最后说一个真实教训。有次批量改配置,脚本里用了sed -i直接替换,结果有台机器的配置文件格式跟其他不一样,替换后语法错误,服务起不来。批量操作前先在单台机器上验证,或者用--dry-run模式跑一遍,确认无误再全量执行。这个习惯让我后来少加了很多班。

分享到: