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

下一篇:函数。