for:遍历列表
# 字面列表
for fruit in apple banana cherry; do
echo "$fruit"
done
# 命令替换
for f in $(ls *.txt); do
echo "$f"
done
# 文件 glob(更安全,建议)
for f in *.txt; do
[[ -e "$f" ]] || continue # 没匹配时 *.txt 字面值,跳过
echo "$f"
done
# 序列
for i in {1..10}; do echo $i; done
for i in {0..20..2}; do echo $i; done # 步长 2
for 的 C 风格
for ((i=0; i<10; i++)); do
echo $i
done
while:条件为真就循环
i=0
while (( i < 10 )); do
echo $i
((i++))
done
# 读文件每一行(标准姿势)
while IFS= read -r line; do
echo "行:$line"
done < input.txt
IFS= read -r 是逐行读取的安全姿势:
IFS=防止首尾空白被吃掉-r防止反斜杠转义
错误写法:
# ❌ 这两种都有问题
for line in $(cat file); do ... done # 按空白切,行内空格会断
while read line; do ... done < file # 没保留首尾空白和反斜杠
until:条件为假才循环(少用)
i=0
until (( i >= 10 )); do
echo $i
((i++))
done
等同于 while !。
break / continue
for i in {1..10}; do
if (( i == 5 )); then break; fi # 跳出整个循环
if (( i % 2 == 0 )); then continue; fi # 跳到下次
echo $i
done
实战 1:批量改名
# 把所有 .jpg 转成 .png
for f in *.jpg; do
convert "$f" "${f%.jpg}.png"
done
${f%.jpg} = 去掉末尾 .jpg(参数展开,下一篇细讲)。
实战 2:批量 ping
hosts=(host1 host2 host3)
for h in "${hosts[@]}"; do
if ping -c 1 -W 1 "$h" >/dev/null 2>&1; then
echo "$h ✓"
else
echo "$h ✗"
fi
done
实战 3:等待服务起来
for i in {1..30}; do
if curl -sf http://localhost:8080/health >/dev/null; then
echo "✓ 服务起来了"
break
fi
echo "等待... ($i/30)"
sleep 2
done
实战 4:逐行处理日志
errors=0
while IFS= read -r line; do
if [[ "$line" == *ERROR* ]]; then
((errors++))
echo "$line"
fi
done < /var/log/myapp.log
echo "共 $errors 条错误"
⚠ 几个坑
1. 循环里改变量在外面看不到(管道场景)
count=0
cat file | while read line; do
((count++))
done
echo $count # → 0 !!
管道里 while 是子 shell,count 变化外面看不到。
✓ 用进程替换:
count=0
while read line; do
((count++))
done < <(cat file)
echo $count # → N
或者直接 < file(前面例子已经这么写)。
2. for 遍历命令输出按空格切,不按行
# ❌ 文件名有空格时炸
for f in $(ls); do ...
# ✓ 用 glob
for f in *; do ...
# ✓ 大量文件用 while + read
find . -name "*.txt" -print0 | while IFS= read -r -d '' f; do
echo "$f"
done
下一篇:函数。