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(需评估副作用)
练习#
- 编写脚本遍历
/etc下所有.conf文件,统计每个文件行数并输出到/tmp/conf_lines.txt。 - 用
while读取/etc/passwd,输出 UID 大于 1000 的用户名。 - 结合
break和continue,实现 1~20 的循环:跳过偶数,遇到 15 停止。