循环语句

Shell 循环语句:for、while、until 及循环控制

循环是 Shell 脚本中实现重复执行任务的核心结构,Bash 提供了 forwhileuntil 三种循环语句,以及 breakcontinue 等控制命令。掌握这些工具能高效处理批量任务(如文件处理、日志分析等)。

for 循环:遍历列表或范围

for 循环适合已知循环对象集合的场景,有两种语法形式。

列表遍历语法(基础形式)

for 变量 in 列表
do
  命令1
  命令2
done
  • 列表:可以是直接给出的值、文件通配符、命令输出等。
  • 变量:每次循环从列表中取一个值赋值给变量。
示例 1:遍历固定值列表
#!/bin/bash
# 遍历一周的天数
for day in 周一 周二 周三 周四 周五 周六 周日
do
  echo "今天是 $day"
done

输出:

今天是 周一
今天是 周二
...
今天是 周日
示例 2:遍历文件列表
#!/bin/bash
# 列出当前目录下所有.sh文件
for script in *.sh
do
  echo "找到脚本:$script"
done
示例 3:遍历命令输出
#!/bin/bash
# 遍历系统用户(取/etc/passwd的第一列)
for user in $(cat /etc/passwd | cut -d: -f1)
do
  echo "系统用户:$user"
done

C 语言风格语法(数值范围)

for (( 初始值; 循环条件; 变量变化 ))
do
  命令
done

适合需要精确控制循环次数的场景(如从 1 循环到 100)。

示例:计算 1 到 10 的和
#!/bin/bash
sum=0
for (( i=1; i<=10; i++ ))
do
  sum=$((sum + i))  # 累加
done
echo "1到10的和为:$sum"  # 输出:55

字段分隔符(IFS)的影响

默认情况下,Shell 会以空格、制表符、换行符作为字段分隔符拆分列表。如需修改(如仅按换行符拆分),可临时修改 IFS 变量:

#!/bin/bash
# 按换行符拆分(处理含空格的文件名)
IFS=$'\n'  # 仅将换行符作为分隔符
for file in $(ls "我的文档")  # 目录名含空格
do
  echo "文件:$file"
done
unset IFS  # 恢复默认分隔符

while 循环:条件为真时循环

while 循环在条件为真时重复执行命令,适合未知循环次数但已知终止条件的场景。

语法

while [ 循环条件 ]
do
  命令
done
示例 1:1 到 10 累加(与 for 循环对比)
#!/bin/bash
sum=0
i=1
while [ $i -le 10 ]  # 当i<=10时循环
do
  sum=$((sum + i))
  i=$((i + 1))  # 变量自增
done
echo "1到10的和为:$sum"  # 输出:55
示例 2:交互式输入循环
#!/bin/bash
# 持续接收用户输入,直到输入"exit"
while true  # 条件恒为真,无限循环
do
  read -p "请输入命令(输入exit退出):" cmd
  if [ "$cmd" = "exit" ]; then
    break  # 退出循环
  fi
  echo "你输入了:$cmd"
done

until 循环:条件为假时循环

until 循环与 while 相反,条件为假时循环,条件为真时终止,适合 “直到某个条件满足才停止” 的场景。

语法

until [ 循环条件 ]
do
  命令
done
示例:1 到 10 累加(与 while 对比)
#!/bin/bash
sum=0
i=1
until [ $i -gt 10 ]  # 当i>10时停止(条件为真时终止)
do
  sum=$((sum + i))
  i=$((i + 1))
done
echo "1到10的和为:$sum"  # 输出:55

循环控制:break、continue、exit

break:跳出循环

  • break:跳出当前循环,执行循环后的命令。
  • break n:跳出 n 层循环(默认 n=1,即最内层循环)。
示例:单层循环跳出
#!/bin/bash
for num in 1 2 3 4 5
do
  if [ $num -eq 3 ]; then
    break  # 当num=3时跳出循环
  fi
  echo "num = $num"
done
# 输出:
# num = 1
# num = 2
示例:多层循环跳出
#!/bin/bash
# 双层循环,break 2 跳出两层
for i in 1 2 3
do
  for j in a b c
  do
    if [ "$j" = "b" ]; then
      break 2  # 直接跳出两层循环
    fi
    echo "i=$i, j=$j"
  done
done
# 输出:i=1, j=a

continue:跳过本次循环

跳过当前循环中的剩余命令,直接进入下一次循环。

示例:跳过特定值
#!/bin/bash
for num in 1 2 3 4 5
do
  if [ $num -eq 3 ]; then
    continue  # 当num=3时跳过本次循环
  fi
  echo "num = $num"
done
# 输出:
# num = 1
# num = 2
# num = 4
# num = 5

exit:退出脚本

直接终止整个脚本的执行,可指定退出状态码(exit 0 表示成功,非 0 表示失败)。

示例:遇到错误退出脚本
#!/bin/bash
for file in file1.txt file2.txt file3.txt
do
  if [ ! -f "$file" ]; then
    echo "错误:文件 $file 不存在!"
    exit 1  # 非0状态码表示执行失败
  fi
  echo "处理文件:$file"
done

循环的典型应用场景

批量文件处理

#!/bin/bash
# 为所有.log文件添加时间戳后缀
for log in *.log
do
  [ -f "$log" ] || continue  # 跳过非文件
  mv "$log" "${log%.log}_$(date +%F).log"
done

日志分析(统计关键词出现次数)

#!/bin/bash
# 统计access.log中各IP的访问次数
declare -A ip_counts  # 声明关联数组

while read line; do
  ip=$(echo $line | awk '{print $1}')  # 提取IP
  ip_counts[$ip]=$(( ${ip_counts[$ip]:-0} + 1 ))  # 累加计数
done < access.log  # 从文件读取内容

# 输出结果
for ip in "${!ip_counts[@]}"; do
  echo "$ip: ${ip_counts[$ip]}次"
done

定时任务(配合 sleep)

#!/bin/bash
# 每5秒检查一次服务状态,共检查10次
count=0
while [ $count -lt 10 ]
do
  echo "[$(date +%T)] 检查服务状态..."
  # 实际场景中替换为真实的服务检查命令
  sleep 5  # 暂停5秒
  count=$((count + 1))
done