在Linux系统管理和自动化脚本编写中,循环结构是Shell编程的核心组成部分。它允许我们重复执行特定代码块,直到满足退出条件为止。不同于其他编程语言,Shell脚本中的循环语法有其独特之处,特别适合处理文件批量操作、系统监控等场景。
Shell主要支持三种循环结构:
每种循环结构都有其最佳实践场景,理解它们的细微差别能显著提升脚本效率。比如处理日志文件时,for循环配合通配符能轻松实现批量处理;而监控系统服务时,while循环搭配sleep命令则是更优选择。
标准for循环的语法格式为:
bash复制for 变量 in 值列表
do
命令序列
done
典型示例:批量创建用户
bash复制for user in bob alice tom
do
useradd $user
echo "User $user created"
done
注意:值列表中的元素默认以空格分隔,若元素本身包含空格,需用引号包裹
bash复制for i in {1..10} # 1到10
for j in {01..15} # 01到15
for k in {1..10..2} # 步长为2
bash复制for ((i=0; i<10; i++))
do
echo "Count: $i"
done
bash复制for file in *.log
do
gzip "$file"
done
实战经验:处理含空格文件名时,务必加双引号,否则会拆分成多个参数
while循环语法:
bash复制while [ 条件 ]
do
命令序列
done
典型应用:读取文件内容
bash复制while read line
do
echo "Processing: $line"
done < data.txt
bash复制while true
do
if ! pgrep -x "nginx" > /dev/null
then
systemctl start nginx
fi
sleep 60
done
bash复制while :
do
clear
echo "1. Option 1"
echo "2. Option 2"
echo "3. Exit"
read -p "Choose: " choice
case $choice in
1) command1 ;;
2) command2 ;;
3) break ;;
*) echo "Invalid option" ;;
esac
sleep 1
done
避坑指南:无限循环必须包含退出条件或sleep,否则会耗尽CPU资源
until与while逻辑相反,当条件为假时执行:
bash复制until [ 条件 ]
do
命令序列
done
典型用例:等待服务启动
bash复制until systemctl is-active --quiet mysql
do
echo "Waiting for MySQL..."
sleep 5
done
| 场景 | 推荐循环类型 | 原因 |
|---|---|---|
| 等待条件满足 | until | 语义更直观 |
| 条件满足时持续执行 | while | 避免双重否定逻辑 |
| 不确定循环次数 | while | 更符合常规编程习惯 |
bash复制for i in {1..100}
do
if [ -f "/tmp/stop.flag" ]
then
break
fi
# 其他操作
done
bash复制while read line
do
[[ $line == \#* ]] && continue # 跳过注释行
process "$line"
done < config.cfg
bash复制# 低效写法
for file in *
do
basename=$(basename "$file")
ext="${basename##*.}"
# ...
done
# 高效写法
for file in *
do
ext="${file##*.}"
# ...
done
bash复制# 低效方式
for user in $(cat users.list)
do
chage -E 2024-12-31 "$user"
done
# 高效方式
chage -E 2024-12-31 $(cat users.list)
bash复制for ip in 192.168.1.{1..254}
do
{
ping -c1 "$ip" &>/dev/null && echo "$ip is up"
} &
done
wait
bash复制find . -type f | while read file
do
count=$((count+1))
done
echo "Total: $count" # 输出为空!
解决方案:使用进程替换或全局变量
bash复制for file in $*
do
# 当参数包含通配符时会意外展开
done
正确做法:始终用双引号包裹"$@"
bash复制#!/bin/bash -x # 显示执行过程
# 或
set -x
for i in {1..3}
do
echo "Iteration $i"
done
set +x
bash复制while IFS= read -r line
do
printf "%q\n" "$line" # 显示转义后的内容
done < data.txt
bash复制start=$SECONDS
# 循环代码
echo "耗时: $((SECONDS-start))秒"
目录遍历示例:
bash复制for dir in /opt/*/
do
echo "Processing $dir"
for file in "$dir"*.log
do
[ -e "$file" ] || continue
process_log "$file"
done
done
复杂条件判断:
bash复制while [ $retry -lt 3 ] && [ $status -ne 0 ]
do
perform_operation
status=$?
((retry++))
done
批量处理数组元素:
bash复制servers=(web1 web2 db1 db2)
for server in "${servers[@]}"
do
ssh "$server" "uptime"
done
选择循环类型的原则:
安全性建议:
可读性优化:
个人经验:在日志分析脚本中,while read循环配合IFS调整,处理CSV文件比for循环更可靠。对于大规模文件处理,先用find生成文件列表再循环,比直接使用通配符更稳定