5.3.2 循环控制与遍历处理

循环控制与遍历处理#

本节围绕 Shell 循环的执行流程、遍历对象与安全写法展开,并给出可直接执行的脚本示例、排错方法与练习。

原理草图:循环执行流程#

文章图片

for 循环基础与命令解释#

  • 遍历列表:用于固定项
for i in a b c; do
  echo "item=$i"
done
# 预期输出:item=a / item=b / item=c
  • 遍历命令输出:输出会按空白分割
for f in $(ls /var/log); do
  echo "log=$f"
done
  • C 风格:控制计数
for ((i=1;i<=5;i++)); do
  echo "num=$i"
done
# 解释:i=1 初始化;i<=5 条件;i++ 每次加1

while / until 循环基础与命令解释#

  • while:条件为真时执行
while read -r line; do
  echo "line=$line"
done < /etc/hosts
# 解释:-r 防止反斜杠转义;< 文件重定向作为输入
  • until:条件为假时执行
until ping -c1 127.0.0.1 >/dev/null 2>&1; do
  echo "waiting for network..."
  sleep 1
done
# 解释:直到 ping 成功(返回码0)才结束

安全遍历文件与目录(推荐写法)#

  • 使用 find + -print0 + read -d '',避免空格/特殊字符问题
find /var/log -type f -name "*.log" -print0 | while IFS= read -r -d '' f; do
  echo "found=$f"
done
  • glob 模式遍历:先判断文件是否存在
for f in /var/log/*.log; do
  [ -e "$f" ] && echo "logfile=$f"
done

数组与关联数组遍历#

arr=(nginx mysql redis)
for s in "${arr[@]}"; do
  echo "service=$s"
done

declare -A map=([host]=127.0.0.1 [port]=3306)
for k in "${!map[@]}"; do
  echo "$k=${map[$k]}"
done

控制语句与效果#

for i in {1..5}; do
  [ "$i" -eq 3 ] && continue
  [ "$i" -eq 5 ] && break
  echo "i=$i"
done
# 预期输出:i=1 i=2 i=4

典型运维脚本:批量重启与日志归档#

  • 1) 批量重启 Nginx(含超时与失败统计)
cat >/tmp/hosts.txt <<'EOF'
127.0.0.1
192.168.1.10
EOF

ok=0
fail=0
for host in $(cat /tmp/hosts.txt); do
  ssh -o ConnectTimeout=3 "$host" "systemctl restart nginx" && {
    echo "[$host] restart OK"
    ok=$((ok+1))
  } || {
    echo "[$host] restart FAIL"
    fail=$((fail+1))
  }
done
echo "summary: ok=$ok fail=$fail"
  • 2) 批量归档 Nginx 日志(带输出与失败处理)
backup_dir=/backup/nginx
mkdir -p "$backup_dir"

for f in /var/log/nginx/*.log; do
  [ -e "$f" ] || continue
  gzip -c "$f" > "$backup_dir/$(basename "$f").gz" && {
    : > "$f"
    echo "archived=$f"
  } || {
    echo "archive failed: $f"
  }
done

子进程陷阱与变量保留#

  • 管道会创建子 shell,变量可能丢失
count=0
ls /var/log | while read -r line; do
  count=$((count+1))
done
echo "count=$count"
# 预期输出:count=0(变量在子 shell 内改变)
  • 改用进程替换保留变量
count=0
while read -r line; do
  count=$((count+1))
done < <(ls /var/log)
echo "count=$count"

排错与调试#

  • 启用调试输出与退出码检查
set -x
for f in /tmp/*.txt; do
  [ -e "$f" ] || { echo "no files"; break; }
  wc -l "$f"
  echo "exit=$?"
done
set +x
  • 常见错误与处理:
  • 文件名含空格:使用 find -print0 + read -d ''
  • 循环内命令失败不退出:手动检查 $? 或使用 set -e(需评估副作用)

练习#

  1. 编写脚本遍历 /etc 下所有 .conf 文件,统计每个文件行数并输出到 /tmp/conf_lines.txt
  2. while 读取 /etc/passwd,输出 UID 大于 1000 的用户名。
  3. 结合 breakcontinue,实现 1~20 的循环:跳过偶数,遇到 15 停止。