# Shell 循环控制 - for-while

# 1.for 循环

# 1.1 什么是循环
  • 脚本在执行任务的时候,总会遇到需要循环执行的时候。
  • 场景:批量创建 100 个用户,我们就需要使用循环来实现
# 1.2 什么是 for 循环
  • 很多人把 for 循环叫做条件循环;
  • 因为 for 循环的次数和给予的条件是成正比的,也就是你给 5 个条件,那么他就循环 5 次;
# 1.3 for 循环基础语法
#1.for 循环基础语法示例
[root@web01 ~]# cat for1.sh 
#!/bin/bash
for i in a b c 
do
        echo $i
done
#2.for 循环基础语法示例
[root@web01 ~]# cat for1.sh 
#!/bin/bash
for i in {1..10}
do
        echo $i
done
#3.for 循环基础语法示例
[root@web01 ~]# cat for1.sh 
#!/bin/bash
for i in $(seq 10)
do
        echo $i
done
#4.for 循环基础语法示例
[root@web01 ~]# cat for1.sh 
#!/bin/bash
for i in $(seq 1 2 10)	# 设置步长
do
        echo $i
done
#4.for 循环基础语法示例
[root@web01 ~]# cat for1.sh 
#!/bin/bash
for ((i=1;i<=10;i++))
do
    echo $i
done

for 循环默认使用空格为分隔符,可以使用 IFS 来自定义分隔符

  • 以冒号做分隔符 IFS=:
  • 以换行符做为字段分隔符 IFS=$'\n'
[root@web01 ~]# cat for1.sh 
#!/bin/bash
IFS=$'\n'
for i in $(cat /etc/hosts)
do
        echo $i
done
# 1.4 for 循环通过文件创建用户脚本
[root@web01 ~]# cat user.txt
xuyong:123456
zhangwuji:123456
zhaomin:123456
xiexun:123456
yangxiao:123456
[root@web01 ~]# cat for-4.sh 
#!/bin/bash
#********************************************************************
#Author     : XuYong
#Date       : 2025-09-25
#FileName   : for-4.sh
#Description: The test script
#********************************************************************
. /etc/init.d/functions
for i in $(cat /root/user.txt)
do
    user=$(echo $i | awk -F ':' '{print $1}')
    pass=$(echo $i | awk -F ':' '{print $2}')
    id ${user} &> /dev/null
    if [ $? -ne 0 ];then
        useradd ${user}
        echo "$pass" | passwd --stdin $user &>/dev/null
        action "User Created Successfully" /bin/true
    else
        action "User Already Exists" /bin/false
    fi
done
# 1.5 for 循环输出整数升序降序脚本

需求:同时输出 1-9 的升序和降序

[root@web01 ~]# cat for-5.sh 
#!/bin/bash
a=9
b=1
for i in {1..9}
do
	echo $a $b
	#let a--
	#let b++
	a=$[ $a -1 ]
	b=$[ $b + 1 ] 
done
# 1.6 for 循环计算 10 以内整除 3 脚本
[root@client ~]# cat for-6.sh 
#!/bin/bash
num=0
for i in $(seq 10)
do
	sum=$[ $i % 3]
	if [ $sum  -eq 0 ];then
		num=$[ $num + $i ]
	fi
done
echo $num
# 1.7 for 循环计算 1+2+3+n 的值脚本
[root@client ~]# echo {1..8769} | xargs | sed 's# #+#g' | bc
[root@client ~]# cat for-7.sh 
#!/bin/bash
sum=0
for i in {1..8769}
do
	sum=$[ $sum + $i ]
done
echo $sum

# 2.for 循环案例

# 2.1 探测主机存活性脚本

需求 1:

  1. 批量探测某个网段的主机存活状态;
  2. 通过 for 循环遍历出所有的 IP 地址;
[root@client ~]# cat for-8.sh 
#!/bin/bash
for i in {1..254}
do
   {
	ip=192.168.40.$i
	ping -c1 -W1 $ip &>/dev/null
	if [ $? -eq 0 ];then
		echo "$ip ok" >> ip_ok.txt
		echo "$ip ok"
	fi
    }&
done
wait
echo "done.."

需求 2:将所有的 IP 地址写入到一个文本文件中,批量探测主机存活状态

[root@client ~]# seq 254 | sed -r 's#(.*)#192.168.40.\1#g' > ip.tx
[root@client ~]# cat for-9.sh 
#!/bin/bash
for ip in $(cat ip.txt)
do
   {
    ping -c1 -W1 $ip &>/dev/null
    if [ $? -eq 0 ];then
        echo "$ip ok" >> ip_ok.txt
        echo "$ip ok"
    fi
    }&
done
wait
echo "done.."

需求 3:批量探测某个网段的主机存活状态,要求判断三次,如果三次失败则失败;

[root@client ~]# cat for-8.sh 
 #!/bin/bash
 for i in {1..254}
 do
     ip=192.168.40.$i
 
     ping -c1 -W1 $ip &>/dev/null
     if [ $? -ne 0 ];then
         for j in {1..3}
         do
                 ping -c1 -W1 $ip &>/dev/null
 
             if [ $? -eq 0 ];then
                 echo "$ip探测第$j次才OK"
                 break
             else
                 echo "$ip探测$j次不通"
             fi
         done
     else
                 echo "$ip 通" 
     fi
 done
# 2.2 探测主机开放端口脚本

需求:

  1. 有一个 ip.txt 的文件,里面有很多 IP 地址。
  2. 有一个 port.txt 的文件,里面有很多端口号。
  3. 现在希望对 ip.txt 的每个 IP 地址进行 port.txt 文件中的端口号进行挨个探测。
  4. 最后将开放的端口和 IP 保存到一个 ok.txt 文件。
[root@client ~]# cat for-8.sh
#!/bin/bash
#外层循环
for ip in $(cat ip.txt)
do
    # 内循环
    for port in $(cat port.txt)
    do
        # 探测 ip+pprt
        nc -z $ip $port &>/dev/null
        if [ $? -eq 0 ];then
            if [ $port == "80" ];then
                echo -e "\e[31m $ip$port开放了,比较危险..\e[0m"
            else
                echo -e "\e[32m $ip$port 开放了.. \e[0m"
            fi
        fi
    done
done
# 2.3 获取系统普通用户脚本

需求:获取系统的所有用户并输出。效果如下:

  • This is 1 user: root
  • This is 2 user: bin
  • This is 3 user: daemon
  • This is 4 user: adm
  • ..............
[root@client ~]# cat for-9.sh 
#!/bin/bash
IFS=$'\n'
index=1
for i in $(cat /etc/passwd)
do
      	user=$(echo $i | awk -F ':' '{print $1}')
        echo "This is ${index} user: ${user}"
      	index=$[ $index+1 ]
done
# 2.4 获取普通用户对应的组脚本

需求:

  1. 获取已存在的普通用户对应的基本组以及附加组
  2. 用户名称: u1, 基本组: 1002 (u1), 附加组:1001 (grp1) 1007 (oldxu)
  3. 用户名称: u2, 基本组: 1003 (u2), 附加组:1001 (grp1)
  4. 用户名称: oldxu, 基本组: 1007 (oldxu), 附加组:Null
#!/usr/bin/bash
LANG=en
pass_file=/etc/passwd
#1. 获取用户名称
users=$(awk -F ':' '{print $1}' ${pass_file})
for i in $users
do
    #3. 获取用户的基本组
    group=$(id ${i} | xargs -n1 | grep  "groups" | awk -F '=' '{print $2}' | tr "," "\n" | awk 'NR==1')
    #4. 获取附加组
    groups=$(id ${i} | xargs -n1 | grep  "groups" | awk -F '=' '{print $2}' | tr "," "\n" | awk 'NR>1' | xargs)
    if [ -z "$groups" ];then
        echo "用户名称: ${i}, 基本组: ${group}, 附加组: Null"
    else
        echo "用户名称: ${i}, 基本组: ${group}, 附加组: ${groups}"
    fi
done
# 2.5 对 MySQL 数据库进行备份

场景 1:备份 MySQL 数据库,将每个库都备份一个 sql 文件,存储至 /backup/mysql/2025-9-27/xx.sql

[root@client ~]# cat for-10.sh 
#!/usr/bin/bash
. /etc/init.d/functions
Db_Name=$(mysql -uroot -e "show databases;" | sed 1d | egrep -v "*_schema")
Date=$(date +%F)
DB_Dir=/backup/mysql/${Date}
# 确保备份的目录是存在的
if [ ! -d ${DB_Dir} ];then
    mkdir -p ${DB_Dir}	
fi
# 备份业务逻辑
for database in ${Db_Name}
do
    mysqldump -uroot -B ${database} > ${DB_Dir}/${database}.sql
    
    # 判断文件是否有内容
    if [ -s ${DB_Dir}/${database}.sql ];then
        action "${DB_Dir}/${database}.sql 备份成功" /bin/true
    else
        action "${DB_Dir}/${database}.sql 备份失败" /bin/false
    fi
done

场景 2:对 MySQL 数据库进行分库分表备份,存储至 /backup/mysql/2025-9-27/database/tables.sql

[root@client ~]# cat for-10.sh 
#!/usr/bin/bash
. /etc/init.d/functions
DB_Name=$(mysql -uroot -e "show databases;" | sed 1d | egrep -v "*_schema")
Date=$(date +%F)
DB_Dir=/backup/mysql/${Date}
# 外循环,提取库的名称
for database in ${DB_Name}
do
	# 创建库对应的备份目录
	if [ ! -d $DB_Dir/$database ];then
  		mkdir -p "$DB_Dir/$database"
	fi
	# 提取表名称
	TB_Name=$(mysql -e "use ${database};show tables;" | sed 1d)
	
	# 内循环,基于库名称然后取获取表名称
	for table in ${TB_Name}
	do
		mysqldump -uroot ${database} ${table} > $DB_Dir/${database}/${table}.sql
		if [ -s $DB_Dir/${database}/${table}.sql ];then
    			action "$DB_Dir/${database}/${table}.sql 备份成功" /bin/true
		else
    			action "$DB_Dir/${database}/${table}.sql 备份失败" /bin/false
		fi
	done
done
# 2.6 实现九九乘法表脚本
[root@client ~]# cat for-10.sh 
#!/bin/bash
for i in {1..9}
do
    # 内循环
    for j in {1..9}
    do
          echo -n "$j * $i = $[ $i * $j ] "
          	if [ $i -eq $j ];then
            		echo ""
            		break
          	fi
    done
done
# 2.7 随机点名脚本

需求:

  • 1. 执行脚本拿到一位同学的名字;
  • 2. 被点过的同学,不会在出现第二次;
  • 3. 该点名脚本将所有的同学都点到过之后,文件就空了,空了就提示请重置;
[root@client ~]# cat for-10.sh 
#!/usr/bin/bash
#重置操作
if [ ! -s user.txt ];then
    read -p "所有的同学都被点到过了,是否需要重置 [ yes | no ]:" Action
    if [ ${Action:=yes}  == "yes" ];then
        cat user_new.txt > user.txt
        rm -f user_new.txt	
        echo "重置成功,可以继续"
        exit
    else
        exit
    fi
fi 
file=user.txt
RANDOm=$(echo $RANDOM)
file_line=$(cat ${file}|wc -l)
sj=$[ ${RANDOm} % ${file_line} ]
if [ ${sj} -eq 0 ];then
    sj=$[ $sj + 1 ]
fi
Full_Name=$(sed -n "${sj}p" user.txt)
echo "此次回答问题的是: ${Full_Name}"
# 备份的
    sed -n "${sj}p" user.txt >> user_new.txt
    sed -i "${sj}d" user.txt
# 2.8 模拟恢复误删除文件
[root@client ~]# cat for-10.sh 
#!/usr/bin/bash
. /etc/init.d/functions
pid=23416
thread_id=$(pstree -p ${pid}| egrep -o "[0-9]+")
# thread_id 拿到很多线程的 id 号码
for iid in ${thread_id}
do
	# 线程的一个 ID
	fd=$(ls -l /proc/${iid}/fd | grep "deleted" |awk '{print $9}')
	for id in ${fd}
	do
		# 基于文件描述符,提取对应的文件名称
		fd_file=$(ls -l /proc/${pid}/fd/${id} | awk '{print $11}')
		# 恢复操作
		cat /proc/${pid}/fd/${id} > $fd_file
		
		# 恢复后的文件获取 md5 值
		hf_file_md5=$(md5sum "${fd_file}" 2>/dev/null | awk '{print $1}')
		# 源文件
		src_file_md5=$(md5sum "${fd_file/opt/soft}" 2>/dev/null | awk '{print $1}')
		if [ "${hf_file_md5}" == "${src_file_md5:=xxx}" ];then
			action " $fd_file 成功" /bin/true
		else
			action " $fd_file 失败" /bin/false
		fi
	done
done

# 3.while 循环

# 3.1 什么是 while

while 在 shell 中也是负责循环的语句,和 for 一样。

# 3.2 while 与 for 如何选

因为功能一样,很多人在学习和工作中的脚本遇到循环到底该使用 for 还是 while 呢?很多人不知道,就会出现有人一遇循环就使用 for、有人一遇循环就使用 while,到底选 for 好还是 while 好:

  • 1. 知道循环次数的使用 for,比如一天循环 24 次;
  • 2. 如果不知道要循环多少次,那就用 while,比如猜数字游戏,每个人猜对的次数是未知的
# 3.3 while 循环基础语法

1.while 循环基本使用示例,降序输出 10 到 1 的数字

[root@client ~]# cat while-1.sh
#!/bin/bash
var=10
while [ $var -gt 0 ]
do
    echo $var
    var=$[$var-1]
done

2.while 循环基本使用示例,输出如下图,两数相乘

[root@client ~]# cat while-3.sh
var=9
var2=1
while [ $var -gt 0 ]
do
    echo "$var * $var2 = $[ $var * $var2 ]"
    var=$[ $var -1 ]
    var2=$[ $var2 + 1]
done

3.4 while 嵌套整数比对

循环嵌套整数比对,判断用户输入的数值是否大于 0,如果大于 0,则三秒输出一次大于。

[root@client ~]# cat while-3.sh
#!/usr/bin/bash
read -p "请输入数字: " num
while [ $num -ge 0 ]
do
    echo "大于"
    sleep 3
done
# 3.5 while 嵌套文件比对

循环嵌套文件比较,判断 /tmp/oldxu 文件是否存在。

  • 如果不存在则 3s 输出一次 not found
  • 如果存在自动退出
[root@client ~]# cat while-3.sh
#!/usr/bin/bash
while true
do
    while [ ! -d /tmp/oldxu ]
    do
        echo "not found /tmp/oldxu"
        sleep 3
    done
done
# 3.6 while 嵌套字符比对

循环嵌套字符比较,判断用户输入的用户名,如果不是 root 则一直让其输入

[root@client ~]# cat while-3.sh
#!/usr/bin/bash
read -p "Login: " account
while [ $account != 'root' ]
do
    read -p "Login: " account
done
# 3.7 while 循环控制语句

在使用循环语句进行循环的过程中,有时候需要在未达到循环结束条件时强制跳出循环;

那么 Shell 给我们提供了内置方法来实现该功能:exit、break、continue

# 3.7.1 exit 方法
  • exit,退出整个程序。
  • 当脚本碰到 exit 时,直接退出,剩余不管有多少代码都不执行。
[root@route ~]# cat while1.sh 
#!/usr/bin/bash
for i in {1..3}
do
    echo "123"
    exit
    echo "456"
done
echo "Done....."
[root@route ~]# sh while1.sh 
123
# 3.7.2 break 方法

break,结束当前循环

当脚本碰到 break 时,会结束当前循环,但会执行循环之后的所有的代码。

[root@route ~]# cat while1.sh 
#!/usr/bin/bash
for i in {1..3}
do
    echo "123"
    break
    echo "456"
done
echo "Done....."
[root@route ~]# sh while1.sh 
123
Done.....
# 3.7.3 continue 方法
  • continue 忽略本次循环剩余的所有代码.
  • 当脚本碰到 continue 时,直接进入下一次循环,直到循环结束,然后继续执行循环之后的代码。
[root@route ~]# cat while1.sh 
#!/usr/bin/bash
for i in {1..3}
do
    echo "123"
    continue
    echo "456"
done
echo "Done....."
[root@route ~]# sh while1.sh 
123
123
123
Done.....
# 3.7.4 while 嵌套 continue

需求:循环嵌套 continue,打印 1-9 当数值为 5 则跳过本次循环,继续下一次循环。请分别使用 for 和

while 实现 1234 6789

[root@client ~]# cat while-3.sh
#!/usr/bin/bash
for i in {1..9}
do
    if [ $i -eq 5 ];then
        continue
    fi
    echo $i
done
# 3.7.5 while 嵌套 break

需求:循环嵌套 break,打印 1-9 当数值为 5 则停止。请分别使用 for 和 while 实现。

[root@client ~]# cat while-3.sh
#!/usr/bin/bash
for i in {1..9}
do
    if [ $i -eq 5 ];then
        #continue
        break
    fi
    echo $i
done
# 3.8. while 循环案例
# 3.8.1 猜测随机数字脚本

猜数字游戏

  • 1) 随机输出一个 1-100 的数字
  • 2) 要求用户输入的必须是数字(数字处加判断)
  • 3) 如果比随机数小则提示比随机数小了 大则提示比随机数大了
  • 4) 正确则退出 错误则继续死循环
  • 5) 最后统计猜了多少次(猜对了多少次,失败多少次)
[root@route ~]# cat sj.sh 
#!/bin/bash
sj=$(echo $[ $RANDOM%100+1 ])
echo $sj
index=0
while true
do
	read -p "请输入一个随机数字:" Num
	
	if [[ ! $Num =~ ^[0-9]+$ ]];then
		continue
	fi
	index=$[ $index+1 ]
	
	if [ $Num -eq $sj ];then
		echo "恭喜您猜对了,一共猜了${index}次"
		exit
	fi
	
	if [ $Num -gt $sj ];then
		echo "您猜的数字太大了!!!"	
	else
		echo "您猜的数字太小了!!!"
	fi
done
# 3.8.2 破解随机字符串脚本

字符串 efbaf275cd、4be9c40b8b、44b2395c46 是通过对随机数变量 RANDOM 随机执行命令: echo$RANDOM|md5sum|cut -c1-10 后的结果,请破解这些字符串对应的 RANDOM 值。

[root@route ~]# cat while2.sh
#!/bin/bash
for i in efbaf275cd 4be9c40b8b 44b2395c46
do
	num=0
	while true
	do 
		md5_sum=$(echo ${num}|md5sum|cut -c1-10) 
		if [ ${md5_sum} == $i ];then
			echo "${num}加密后结果是${md5_sum}"
			break
		else
			num=$[ $num + 1 ]
			continue
		fi
	done
done
[root@route ~]# sh while2.sh 
15000加密后结果是efbaf275cd
12000加密后结果是4be9c40b8b
9000加密后结果是44b2395c46
# 3.8.3 通过文本创建用户脚本
  • 通过读入文件中的用户与密码文件,进行批量添加用户。

  • 文件中的格式: user:passwd

[root@client ~]# cat while-3.sh
#!/usr/bin/bash
# for 是按照空格作为分隔符读取
# while 是按照行来读取的文件
while read line
do
    user=$(echo $line | awk -F ':' '{print $1}')
    pass=$(echo $line | awk -F ':' '{print $2}')
    id $user &>/dev/null
    if [ $? -ne 0 ];then
        useradd $user 
        echo "$pass" | passwd --stdin $user &>/dev/null
        echo "$user $pass created successfully"
    fi   
done<user.txt
# 3.8.5 防止 ssh 暴力破解脚本
  • 如果使用 oldxu 用户登录系统,则立即将其踢出,然后将其拉入黑名单,以防止该用户继续使用该 IP 地址进行登录。
  • 将 sshd:username ip 地址 内容写至 /etc/hosts.deny,则可拒绝用户登陆系统。
[root@route ~]# cat deny_user.sh
#!/usr/bin/bash
. /etc/init.d/functions
index=0
while true
do
    User=oldxu
    login_user=$(who | grep ${User} | wc -l)
    deny_file=/etc/hosts.deny
    if [ $login_user -gt 0 ];then
        # 说明用户是存在
        login_user_ip=$(who | grep ${User} | awk '{print $NF}' | awk -F '[()]' '{print $2}'|sort |uniq)
        # 写入限制
        echo "sshd:${User} ${login_user_ip}" >> ${deny_file}
        
        # 剔除用户
        pkill -9 -u ${User}
        action  "探测 ${User} 已经登录系统, 已被干掉" /bin/false
    else
        index=$[ $index + 1]
        action "探测 ${User} 是否登录系统, 第 ${index} 次,目前正常" /bin/true
        sleep 3
        continue
    fi
done
# 3.8.6 9*9 乘法表
[root@route ~]# cat while.sh 
#!/bin/bash
num1=1
while [ $num1 -le 9 ]
do
	num2=1
	#内循环小于等于外循环,则条件成立
	while [ $num2 -le $num1 ]
	do
		echo -n "$num1 * $num2 = $[ $num1* $num2 ]  "
		num2=$[ $num2 +1 ]
	done
		echo "" 
	num1=$[ $num1 + 1 ]
done
此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Xu Yong 微信支付

微信支付

Xu Yong 支付宝

支付宝