Linux Shell编程及自动化运维实现-3-循环

如果让你创建 10 个用户,你会写 10 行 useradd 吗?如果让你检测 254 台主机是否在线,你会 ping 254 次吗?

当然不会。这就是循环的意义——让计算机帮你做重复的事情。

本文将系统讲解 Shell 的三种循环结构(for、while、until),以及实现免交互自动化的 Expect 工具。通过实战案例(主机扫描、批量创建用户、批量修改密码、公钥推送)带你掌握循环与自动化运维的完整知识体系。


for 循环

语法结构

for 变量名 [in 取值列表]
do
    循环体
done
组成部分说明
for循环关键字
变量名每次循环时,变量会被赋值为取值列表中的一个值
in 取值列表可选,变量的取值范围
do...done循环体,每次循环执行的命令

取值列表的多种写法

# 方式 1:花括号展开
for i in {1..10}

# 方式 2:seq 命令
for i in $(seq 1 10)

# 方式 3:反引号 seq
for i in `seq 10`

# 方式 4:直接列举
for i in a b c d e

# 方式 5:命令结果
for i in $(ls /etc/)

案例 1:自动创建 10 个用户

需求:自动循环创建 10 个用户(user1 到 user10)。

#!/bin/bash

for i in {1..10}
do
    useradd "user$i"
    echo "user$i is created"
done

等价写法:

# 使用 seq 命令
for i in $(seq 1 10)
do
    useradd "user$i"
done

# 使用反引号
for i in `seq 10`
do
    useradd "user$i"
done

案例 2:ping 测试全网主机

需求:通过循环工具,探测全网主机。将在线主机记录在文本中。

IP 地址列表:

[root@localhost scripts]# cat ip.txt
172.16.8.100
172.16.8.4
172.16.100.254

思路

  1. 编写常规 ping 测试脚本(无循环)
  2. 添加循环语句 for i in {2..254}
  3. 优化脚本(后台执行、清空旧记录、循环完成提示、wait 等待)

步骤 1:基础版本(无循环)

#!/bin/bash

ip=192.168.100.10

ping -c1 -W1 $ip &>/dev/null

if [ $? -eq 0 ]; then
    echo "$ip" | tee -a ip.txt
fi

步骤 2:添加循环

#!/bin/bash

for i in {2..254}
do
    ip=192.168.100.$i

    ping -c1 -W1 $ip &>/dev/null

    if [ $? -eq 0 ]; then
        echo "$ip" | tee -a ip.txt
    fi
done

步骤 3:优化版本(生产级)

#!/bin/bash

# 清空记录文本
>ip.txt

for i in {2..254}
do
    {
        ip=192.168.100.$i

        ping -c1 -W1 $ip &>/dev/null

        if [ $? -eq 0 ]; then
            echo "$ip" | tee -a ip.txt
        fi
    }&    # 后台执行
done

wait    # 等待前一个程序执行完毕,再执行下一个命令

echo "finish...."

优化说明:

优化点说明
>ip.txt清空旧记录,避免重复数据
{ ... }&后台执行,大幅提升扫描速度
wait等待所有后台任务完成后再输出提示
ping -c1 -W1只 ping 1 次,超时等待 1 秒,加快速度

对比:串行 ping 254 台主机需要几分钟,后台并发只需要几秒钟。


案例 3:通过用户列表文件创建用户

需求:从文件中读取用户名列表,批量创建用户。

用户名文件:

[root@localhost scripts]# cat user.txt
zhangsan
lisi
wangwu
alice

基础版本

#!/bin/bash

for user in `cat user.txt`
do
    useradd $user
    echo "$user is created"
done

升级版本(生产级)

需求升级:

  • 用户可以使用参数自定义用户名文件
  • 如果用户没有输入用户名文件,提示用户输入
  • 如果用户输入的不是文件,提示用户更正
  • 启动循环创建用户
  • 如果用户已经存在,提示存在
  • 如果用户不存在,则创建成功,提示成功
#!/bin/bash

pass=123

# 判断脚本是否有参数
if [ $# -eq 0 ]; then
    echo "usage: $0 filename"
    exit 1
fi

# 判断用户输入的是否是文件
if [ ! -f $1 ]; then
    echo "error filename"
    exit 2
fi

# 设置循环,读取文件,创建用户
for user in `cat $1`
do
    id $user &>/dev/null

    # 如果用户不存在,则创建用户,否则提示已经存在
    if [ $? -eq 0 ]; then
        echo "user $user already exists"
    else
        useradd $user
        echo "$pass" | passwd --stdin $user &>/dev/null

        # 判断用户是否创建成功,并提示
        if [ $? -eq 0 ]; then
            echo "$user is created."
        fi
    fi
done

使用方式:

[root@localhost ~]# ./create_users.sh user.txt

退出码说明:

退出码含义
0正常退出
1没有提供参数
2提供的参数不是文件

案例 4:批量修改主机 root 密码

前提:

  1. 已经完成密钥登录配置(ssh-keygen)
  2. 定义主机地址列表
  3. 了解远程修改密码的方法

步骤:

  1. 邀请用户输入密码
  2. 循环调用 IP 地址
  3. 执行远程修改
  4. 记录执行结果

免密码登录准备:

# 生成密钥
ssh-keygen

# 分发公钥
ssh-copy-id IP地址

批量修改密码脚本:

#!/bin/bash

# 邀请用户输入新密码
read -p "请用户输入新密码: " password

# 循环调用 IP 地址
for i in $(cat ip.txt)
do
    {
        # 测试 IP 是否在线
        ping -c1 -W1 $i &>/dev/null

        # 在线 IP 进行修改
        if [ $? -eq 0 ]; then
            # 修改密码
            ssh $i "echo $password | passwd --stdin root"

            # 修改成功与否并记录
            if [ $? -eq 0 ]; then
                echo "$i" >> ok.txt
            else
                echo "$i" >> fail.txt
            fi
        # 不在线的主机记录
        else
            echo "$i" >> meigai.txt
        fi
    }&
done

wait
echo "批量密码修改完成!"

记录文件说明:

文件含义
ok.txt修改成功的主机 IP
fail.txt修改失败的主机 IP
meigai.txt未在线、未修改的主机 IP

while / until 循环

while 循环

特点:循环次数不一定是固定的。

语法:

while 条件测试
do
    循环体
done

当条件测试成立(条件测试为真),执行循环体。

案例 1:死循环计数

#!/bin/bash

while :
do
    let i++
    sleep 1
    echo $i
done

说明:

符号说明
:永远为真,构成死循环
let i++变量 i 自增 1
sleep 1等待 1 秒

案例 2:条件循环

#!/bin/bash

while [ $i -lt 10 ]
do
    let i++
    echo $i
done

until 循环

语法:

until 条件测试
do
    循环体
done

当条件测试成立(条件测试为视为成立),执行循环体。

while 和 until 的区别:

循环类型条件为真时条件为假时
while执行循环体退出循环
until退出循环执行循环体

案例:循环到指定数字

需求:每秒循环一个数字,直到 14 停止。

#!/bin/bash

until [[ $i -eq 14 ]]
do
    let i++
    sleep 1
    echo $i
done

等价 while 写法:

#!/bin/bash

while [[ $i -ne 14 ]]
do
    let i++
    sleep 1
    echo $i
done

三种循环总结

循环类型适用场景特点
for循环次数固定遍历列表,逐一处理
while循环次数不固定条件为真时持续执行
until循环次数不固定条件为假时持续执行

选择建议

  • 知道要循环多少次 → 使用 for
  • 不知道循环多少次,满足条件就继续 → 使用 while
  • 不知道循环多少次,满足条件就停止 → 使用 until

Expect 自动化交互

前言

观察 ssh 登录的交互现象:

[root@localhost ~]# ssh root@192.168.100.123
The authenticity of host '192.168.100.123 (192.168.100.123)' can't be established.
ECDSA key fingerprint is SHA256:3fjgqAxLwRbNDXSOUh1+/JrI4KpzyrHgkzmymB4vUUM.
ECDSA key fingerprint is MD5:3e:f6:f6:dc:fa:ef:26:47:9e:70:2f:4d:39:38:0b:0c.
Are you sure you want to continue connecting (yes/no)? 

有些程序难以避免的需要交互。你该如何解决脚本与程序的交互问题?

解决方案:使用 Expect 工具实现自动交互。


什么是 Expect

Expect 是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。

项目说明
作者Don Libes(1990 年开始编写)
用途自动实现交互功能的软件套件
原理根据程序的提示,模拟标准输入提供给程序需要的输入
应用场景ssh 登录、ftp 上传、telnet 远程管理等需要交互的场景

简单理解:Expect 就是帮你在脚本中”自动打字”的工具。


安装

yum install -y expect tcl tclx tcl-devel

Expect 核心命令

命令说明
spawnExpect 内部命令,启动一个 shell 程序
expect期望程序输出哪些内容
send模拟用户输入,发送内容给程序
exp_continue跳过循环,继续下一条语句
interact允许用户交互(留在远程会话中)
expect eof等待会话结束

示例 1:SSH 免交互登录

#!/usr/bin/expect

spawn ssh root@192.168.100.123

expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "111111\r" }
}

interact

逐行解释:

代码含义
#!/usr/bin/expect指定 Expect 解释器
spawn ssh root@192.168.100.123启动 ssh 连接
"yes/no" { send "yes\r" }如果看到 yes/no 提示,发送 yes + 回车
exp_continue继续匹配下一条(匹配完 yes/no 后继续匹配 password)
"password:" { send "111111\r" }如果看到 password 提示,发送密码 + 回车
interact登录成功后,允许用户交互

测试验证:

[root@localhost ~]# chmod +x ssh_login.exp
[root@localhost ~]# ./ssh_login.exp
spawn ssh root@192.168.100.123
The authenticity of host '192.168.100.123 (192.168.100.123)' can't be established.
ECDSA key fingerprint is SHA256:3fjgqAxLwRbNDXSOUh1+/JrI4KpzyrHgkzmymB4vUUM.
ECDSA key fingerprint is MD5:3e:f6:f6:dc:fa:ef:26:47:9e:70:2f:4d:39:38:0b:0c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.100.123' (ECDSA) to the list of known hosts.
root@192.168.100.123's password: 
Last login: Tue Jun  9 11:19:38 2026 from 192.168.100.1
[root@web2 ~]# 

示例 2:公钥推送(expect 实战)

准备工作

  1. 安装 expect
  2. 准备 SSH 公钥(ssh-keygen)

步骤 1:通过 shell 循环判断在线主机

#!/bin/bash

# 创建一个 IP 地址文件
>ip.txt

# 使用 for 循环 ping 测试主机是否在线
for i in {2..254}
do
    {
        ip=192.168.100.$i
        ping -c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ]; then
            echo "$ip" >> ip.txt
        fi
    }&
done

wait

cat ip.txt    # 观察在线主机

步骤 2:通过 expect 进行交互推送

推送单个主机的 expect 脚本:

#!/usr/bin/expect

set timeout 10

spawn ssh-copy-id 192.168.100.123

expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "111111\r" }
}

expect eof

步骤 3:完整优化脚本(安装 + 公钥准备 + 批量推送)

#!/bin/bash

# 创建一个 IP 地址文件
>ip.txt

# 检测 expect 是否安装
rpm -q expect &>/dev/null
if [ $? -ne 0 ]; then
    yum install -y expect
fi

# 检测公钥是否创建
if [ ! -f ~/.ssh/id_rsa ]; then
    ssh-keygen -P "" -f ~/.ssh/id_rsa
fi

# 使用 for 循环 ping 测试主机是否在线
for i in {2..254}
do
    {
        ip=192.168.100.$i
        ping -c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ]; then
            echo "$ip" >> ip.txt
            /usr/bin/expect <<-EOF
spawn ssh-copy-id $ip
expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "111111\r" }
}
expect eof
EOF
        fi
    }&
done

wait

echo "finish..."

脚本结构说明:

┌─────────────────────────────────────────┐
│  步骤 1:安装 expect                     │
│  步骤 2:检测 SSH 公钥                   │
│  步骤 3:扫描在线主机                    │
│  步骤 4:逐个推送公钥(expect 交互)     │
│  步骤 5:等待全部完成                    │
└─────────────────────────────────────────┘

常见问题

for 循环中 in 可以省略吗?

可以。for i 等同于 for i in "$@"(遍历所有位置参数)。但建议始终写上 in,代码更清晰。

{1..10}$(seq 1 10) 有什么区别?

特性{1..10}$(seq 1 10)
支持变量不支持({1..$n} 无效)支持($(seq 1 $n)
步长不支持支持 $(seq 1 2 10)(1,3,5,7,9)
性能更快(Shell 内置)稍慢(外部命令)

建议:固定范围用 {1..10},变量范围用 $(seq 1 $n)

while : 是什么意思?

: 是 Shell 的内置命令,永远返回真(退出码 0),构成死循环。等价于 while true

expect 脚本可以直接用 bash 执行吗?

不可以。expect 脚本的第一行必须是 #!/usr/bin/expect,使用 ./script.expexpect script.exp 执行。

expect 中可以嵌套 shell 命令吗?

可以。使用 expect <<-EOF ... EOF heredoc 语法,可以在 bash 脚本中内嵌 expect 代码。


总结

通过本章节的学习,你应该掌握了:

  • 理解 for 循环的语法结构和取值列表的多种写法
  • 掌握 for 循环实战:批量创建用户、全网主机 ping 测试、用户列表文件创建用户、批量修改远程密码
  • 理解 while 循环(条件为真时执行)
  • 理解 until 循环(条件为假时执行)
  • 掌握三种循环的区别和选择建议
  • 理解 Expect 工具的作用和原理
  • 掌握 Expect 核心命令(spawn、expect、send、exp_continue、interact)
  • 掌握 SSH 免交互登录脚本编写
  • 掌握批量公钥推送完整流程(安装 → 公钥准备 → 扫描 → 推送)
  • 能够简单编写生产级的批量运维脚本

学习建议:循环和 Expect 是自动化运维的核心技能。建议在虚拟机上完整实践每一个案例:全网主机扫描、批量创建用户、批量修改密码、公钥推送。特别是 expect 的公钥推送,是实际工作中最常用的自动化场景之一。动手做一遍,远胜于阅读十遍文档。


🙋 学习路上不孤单,有坑一起填!



卡在参数调试、命令打架、功能没反应、资源失效?其他问题?直接戳我,也可以进群和同好交流!



📱 个人QQ:3838586495    👥 交流Q群:1094091455    📮 邮箱:leyan2504@163.com



内容帮你省了大把踩坑时间?可以文末小小打赏鼓励一波,也欢迎安利给身边学习的朋友~



✨ 欢迎来评论区「交作业」:分享你的配置思路、提出疑难问题;评论区正确填写邮箱,不错过任何人的解答回复!


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇