在 Shell 脚本编程中,循环是自动化重复任务的核心结构。Bash 提供了三种主要的循环形式:for、while 和 until,它们各有擅长的场景,组合使用可以高效处理文件批处理、逐行读取、等待条件等常见需求。
1. for 循环:遍历列表的利器
for 循环最适合当你有一个明确的项目集合需要逐一处理时。列表可以直接写在命令中,也可以来自变量、通配符扩展或命令的输出结果。
基本语法:
for 变量 in 列表; do
命令
done
常见示例:
① 遍历固定的单词列表:
for fruit in apple banana orange; do
echo "I like $fruit"
done
② 遍历当前目录下所有 .txt 文件,批量改名或压缩:
for file in *.txt; do
gzip "$file"
done
注意:变量引用最好加上双引号,以防文件名包含空格时出错。
③ 遍历命令输出(如某个范围内的数字,或文件的行):
for i in $(seq 1 5); do
echo "Number: $i"
done
④ C 语言风格的算术循环(适用于需要精确控制循环次数时):
for (( i=1; i<=3; i++ )); do
echo "Attempt $i"
done
实战提示:for 循环在执行前会一次性将列表加载到内存中。如果列表来自一个可能产生巨大输出的命令(例如 cat huge_file),建议改用 while 循环逐行读取,避免内存浪费。
2. while 循环:条件为真则持续执行
while 循环在每次迭代前检查一个命令的退出状态码(0 表示真/成功,非0表示假/失败),只要条件命令成功返回,循环就会继续。
基本语法:
while 条件命令; do
命令
done
典型用途:
① 逐行读取文件内容:
while IFS= read -r line; do
echo "Processing: $line"
done < input.txt
这里使用 read 作为条件命令:当读到文件末尾时,read 返回非0状态,循环自动结束。这是处理大文件的标准安全做法。
② 监控某个进程是否在运行,若退出则执行操作:
while pgrep -x "myapp" > /dev/null; do
sleep 5
done
echo "Myapp has terminated."
③ 使用计数器实现有限次循环:
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1))
done
关键要点:
- 必须确保条件命令最终会失败,否则循环会无限执行。
- 用
[ ]或[[ ]]构造测试条件时,注意变量要加双引号,如[ "$var" = "yes" ]。
3. until 循环:条件为假则持续执行
until 和 while 的逻辑正好相反:只要条件命令的退出状态码为非0(假),循环就继续;一旦条件变为成功(0),循环终止。
基本语法:
until 条件命令; do
命令
done
常见应用场景:
① 等待网络连接就绪(例如 MySQL 服务在启动中):
until mysqladmin ping -h 127.0.0.1 --silent; do
echo "Waiting for MySQL..."
sleep 2
done
echo "MySQL is up."
这里 mysqladmin ping 在连接失败时返回非0,循环保持等待;一旦服务响应成功,返回0,循环退出。
② 等待文件被删除或创建:
until [ -f /tmp/ready ]; do
sleep 1
done
echo "File /tmp/ready found, continuing."
③ 代替 while ! 的场景,使逻辑更清晰:
# 用 while 写出 "直到某个条件成立" 需要取反
while ! ping -c1 8.8.8.8 > /dev/null 2>&1; do
sleep 3
done
# 用 until 表达则更直接
until ping -c1 8.8.8.8 > /dev/null 2>&1; do
sleep 3
done
两者的实际效果相同,但 until 读起来更符合自然语言习惯。
三种循环的选择指南
| 场景 | 推荐循环 | 原因 |
|------|----------|------|
| 遍历已知的固定列表(文件、数字、字符串) | for | 简单直观,无需手动管理条件 |
| 逐行读取文件或命令输出,或持续检查运行状态 | while | 安全处理长输出,按需动态判断 |
| 等待某个状态从不成立变为成立(如服务启动) | until | 语义明确,避免取反逻辑 |
共同注意事项:
- 循环体内可以使用
break立即跳出整个循环,或使用continue跳过当前迭代。 - 如果需要无限循环,可以写成
while true; do ...; done或for (( ; ; )); do ...; done,但必须确保内部有可控的退出方式(如通过条件判断调用break)。 - 频繁的循环(例如成千上万次调用外部分命令)会消耗较多 CPU 和进程创建时间,在大批量处理时应尽量利用内置功能和批处理方式优化。
掌握这三种循环,你就能够用脚本处理绝大部分日常运维和数据处理任务。实践中,先用最清晰的写法让脚本运行正确,再根据需要调整性能。