# Shell 循环控制 - for-while
# 1.for 循环
# 1.1 什么是循环
- 脚本在执行任务的时候,总会遇到需要循环执行的时候。
- 场景:批量创建 100 个用户,我们就需要使用循环来实现
# 1.2 什么是 for 循环
- 很多人把 for 循环叫做条件循环;
- 因为 for 循环的次数和给予的条件是成正比的,也就是你给 5 个条件,那么他就循环 5 次;
# 1.3 for 循环基础语法
| |
| [root@web01 ~] |
| |
| for i in a b c |
| do |
| echo $i |
| done |
| |
| |
| [root@web01 ~] |
| |
| for i in {1..10} |
| do |
| echo $i |
| done |
| |
| |
| [root@web01 ~] |
| |
| for i in $(seq 10) |
| do |
| echo $i |
| done |
| |
| |
| [root@web01 ~] |
| |
| for i in $(seq 1 2 10) |
| do |
| echo $i |
| done |
| |
| |
| [root@web01 ~] |
| |
| for ((i=1;i<=10;i++)) |
| do |
| echo $i |
| done |
for 循环默认使用空格为分隔符,可以使用 IFS 来自定义分隔符
- 以冒号做分隔符 IFS=:
- 以换行符做为字段分隔符 IFS=$'\n'
| [root@web01 ~] |
| |
| IFS=$'\n' |
| |
| for i in $(cat /etc/hosts) |
| do |
| echo $i |
| done |
# 1.4 for 循环通过文件创建用户脚本
| [root@web01 ~] |
| xuyong:123456 |
| zhangwuji:123456 |
| zhaomin:123456 |
| xiexun:123456 |
| yangxiao:123456 |
| |
| |
| [root@web01 ~] |
| |
| |
| |
| |
| |
| |
| |
| . /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 ~] |
| |
| a=9 |
| b=1 |
| for i in {1..9} |
| do |
| echo $a $b |
| |
| |
| a=$[ $a -1 ] |
| b=$[ $b + 1 ] |
| done |
# 1.6 for 循环计算 10 以内整除 3 脚本
| [root@client ~] |
| |
| |
| 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 ~] |
| |
| [root@client ~] |
| |
| |
| sum=0 |
| for i in {1..8769} |
| do |
| sum=$[ $sum + $i ] |
| done |
| |
| echo $sum |
# 2.for 循环案例
# 2.1 探测主机存活性脚本
需求 1:
- 批量探测某个网段的主机存活状态;
- 通过 for 循环遍历出所有的 IP 地址;
| [root@client ~] |
| |
| |
| 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 ~] |
| |
| [root@client ~] |
| |
| 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 探测主机开放端口脚本
需求:
- 有一个 ip.txt 的文件,里面有很多 IP 地址。
- 有一个 port.txt 的文件,里面有很多端口号。
- 现在希望对 ip.txt 的每个 IP 地址进行 port.txt 文件中的端口号进行挨个探测。
- 最后将开放的端口和 IP 保存到一个 ok.txt 文件。
| [root@client ~] |
| |
| |
| for ip in $(cat ip.txt) |
| do |
| |
| for port in $(cat port.txt) |
| do |
| |
| 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 ~] |
| |
| |
| 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 获取普通用户对应的组脚本
需求:
- 获取已存在的普通用户对应的基本组以及附加组
- 用户名称: u1, 基本组: 1002 (u1), 附加组:1001 (grp1) 1007 (oldxu)
- 用户名称: u2, 基本组: 1003 (u2), 附加组:1001 (grp1)
- 用户名称: oldxu, 基本组: 1007 (oldxu), 附加组:Null
| #!/usr/bin/bash |
| LANG=en |
| pass_file=/etc/passwd |
| |
| |
| users=$(awk -F ':' '{print $1}' ${pass_file}) |
| |
| for i in $users |
| do |
| |
| group=$(id ${i} | xargs -n1 | grep "groups" | awk -F '=' '{print $2}' | tr "," "\n" | awk 'NR==1') |
| |
| |
| 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 ~] |
| |
| . /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 ~] |
| |
| . /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 ~] |
| |
| |
| 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 ~] |
| |
| |
| |
| 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 ~] |
| |
| . /etc/init.d/functions |
| |
| pid=23416 |
| thread_id=$(pstree -p ${pid}| egrep -o "[0-9]+") |
| |
| |
| for iid in ${thread_id} |
| do |
| |
| 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 |
| |
| |
| 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 ~] |
| |
| var=10 |
| while [ $var -gt 0 ] |
| do |
| echo $var |
| var=$[$var-1] |
| done |
2.while 循环基本使用示例,输出如下图,两数相乘
| [root@client ~] |
| 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 ~] |
| |
| read -p "请输入数字: " num |
| while [ $num -ge 0 ] |
| do |
| echo "大于" |
| sleep 3 |
| done |
# 3.5 while 嵌套文件比对
循环嵌套文件比较,判断 /tmp/oldxu 文件是否存在。
- 如果不存在则 3s 输出一次 not found
- 如果存在自动退出
| [root@client ~] |
| |
| while true |
| do |
| while [ ! -d /tmp/oldxu ] |
| do |
| echo "not found /tmp/oldxu" |
| sleep 3 |
| done |
| done |
# 3.6 while 嵌套字符比对
循环嵌套字符比较,判断用户输入的用户名,如果不是 root 则一直让其输入
| [root@client ~] |
| |
| 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 ~] |
| |
| for i in {1..3} |
| do |
| echo "123" |
| exit |
| echo "456" |
| done |
| echo "Done....." |
| |
| [root@route ~] |
| 123 |
# 3.7.2 break 方法
break,结束当前循环
当脚本碰到 break 时,会结束当前循环,但会执行循环之后的所有的代码。
| [root@route ~] |
| |
| for i in {1..3} |
| do |
| echo "123" |
| break |
| echo "456" |
| done |
| echo "Done....." |
| |
| [root@route ~] |
| 123 |
| Done..... |
# 3.7.3 continue 方法
- continue 忽略本次循环剩余的所有代码.
- 当脚本碰到 continue 时,直接进入下一次循环,直到循环结束,然后继续执行循环之后的代码。
| [root@route ~] |
| |
| for i in {1..3} |
| do |
| echo "123" |
| continue |
| echo "456" |
| done |
| echo "Done....." |
| |
| [root@route ~] |
| 123 |
| 123 |
| 123 |
| Done..... |
# 3.7.4 while 嵌套 continue
需求:循环嵌套 continue,打印 1-9 当数值为 5 则跳过本次循环,继续下一次循环。请分别使用 for 和
while 实现 1234 6789
| [root@client ~] |
| |
| 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 ~] |
| |
| for i in {1..9} |
| do |
| if [ $i -eq 5 ];then |
| |
| break |
| fi |
| echo $i |
| done |
# 3.8. while 循环案例
# 3.8.1 猜测随机数字脚本
猜数字游戏
- 1) 随机输出一个 1-100 的数字
- 2) 要求用户输入的必须是数字(数字处加判断)
- 3) 如果比随机数小则提示比随机数小了 大则提示比随机数大了
- 4) 正确则退出 错误则继续死循环
- 5) 最后统计猜了多少次(猜对了多少次,失败多少次)
| [root@route ~] |
| |
| |
| 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 ~] |
| |
| |
| 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 ~] |
| 15000加密后结果是efbaf275cd |
| 12000加密后结果是4be9c40b8b |
| 9000加密后结果是44b2395c46 |
# 3.8.3 通过文本创建用户脚本
| [root@client ~] |
| |
| |
| |
| |
| 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 ~] |
| |
| . /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 ~] |
| |
| |
| 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 |