在Linux系统管理和自动化脚本编写中,循环结构是提高效率的核心工具。作为Shell脚本的三大基本控制结构之一(顺序、分支、循环),循环语句允许我们重复执行特定代码块,直到满足终止条件。与C、Java等高级语言不同,Shell的循环语法更贴近自然语言,但功能同样强大。
Shell脚本主要支持三种循环结构:
实际工作中,for循环使用频率最高(约60%场景),while约占35%,until仅占5%左右。选择循环类型时应首先考虑业务逻辑的天然表达方式。
for循环的标准语法格式如下:
bash复制for 变量 in 值列表
do
循环体命令
done
典型应用示例——批量处理日志文件:
bash复制#!/bin/bash
for logfile in /var/log/*.log
do
echo "Processing $logfile"
gzip "$logfile"
done
bash复制for i in {1..10}; do echo $i; done
for i in {01..10}; do echo $i; done # 保持两位数格式
bash复制for ((i=0; i<10; i++)); do
echo "Iteration $i"
done
bash复制for i in {1..100..5}; do # 每次递增5
echo $i
done
在旧版Bash中(如CentOS 6默认的4.1版本),数值范围需要用
seq命令替代:bash复制for i in $(seq 1 10); do echo $i; done
以下脚本演示如何通过CSV文件批量创建用户:
bash复制#!/bin/bash
input="users.csv"
while IFS=',' read -r username uid groupname
do
if ! grep -q "^$groupname:" /etc/group; then
groupadd "$groupname"
fi
useradd -u "$uid" -g "$groupname" "$username"
echo "User $username (UID:$uid) created in group $groupname"
done < "$input"
while循环的基本格式:
bash复制while [ 条件测试 ]
do
循环体命令
done
典型应用——监控进程是否存在:
bash复制#!/bin/bash
process="nginx"
while pgrep "$process" >/dev/null
do
echo "$process is running at $(date)"
sleep 5
done
echo "$process has stopped"
bash复制while true; do commands; done
while :; do commands; done
while [ 1 ]; do commands; done
break:立即终止整个循环continue:跳过本次迭代exit:退出整个脚本(慎用)bash复制#!/bin/bash
while true; do
clear
echo "System Administration Menu"
echo "1. Disk Usage"
echo "2. Memory Info"
echo "3. Exit"
read -p "Enter choice [1-3]: " choice
case $choice in
1) df -h ;;
2) free -m ;;
3) break ;;
*) echo "Invalid option" ;;
esac
read -p "Press [Enter] to continue..."
done
until循环与while逻辑相反,格式为:
bash复制until [ 条件测试 ]
do
循环体命令
done
典型应用——等待服务启动:
bash复制#!/bin/bash
until systemctl is-active nginx >/dev/null
do
echo "Waiting for nginx to start..."
sleep 3
done
echo "nginx is now running"
| 特征 | while循环 | until循环 |
|---|---|---|
| 条件判断 | 真时执行 | 假时执行 |
| 适用场景 | 持续满足条件时运行 | 等待条件变为真时运行 |
| 典型应用 | 监控、持续处理 | 等待、超时控制 |
| 使用频率 | 高 | 低 |
处理多维数据时的嵌套循环示例:
bash复制#!/bin/bash
for department in Sales Engineering Support
do
echo "Processing $department:"
for user in $(getent passwd | grep "/home/$department/" | cut -d: -f1)
do
echo " - $user"
done
done
bash复制for i in {1..5}; do
echo "Iteration $i"
done > output.txt
bash复制while read -r line; do
echo "Processing: $line"
done < input.txt
bash复制# 差实践
for file in *; do
basename "$file"
done
# 好实践
for file in *; do
echo "${file##*/}"
done
bash复制# 高效处理大文件
while IFS= read -r line; do
process "$line"
done < large_file.txt
bash复制find . -name "*.txt" | while read file; do
count=$((count+1))
done
echo "Total: $count" # 输出为空!
解决方法:使用进程替换避免子shell
bash复制while read file; do count=$((count+1)) done < <(find . -name "*.txt")
bash复制for file in $(ls); do # 遇到含空格文件名会出错
echo "$file"
done
正确做法:
bash复制for file in *; do echo "$file" done
bash复制#!/bin/bash -x # 启用调试
for i in {1..3}; do
echo "Iteration $i"
done
bash复制set -x # 开始调试
for i in {1..3}; do
echo "Iteration $i"
done
set +x # 结束调试
bash复制for item in "$@"; do
echo "Processing $item (PID:$$ PPID:$PPID)"
done
bash复制# 推荐风格
for dir in /opt/*/; do
[[ -d "$dir" ]] || continue
echo "Found directory: ${dir%/}"
done
bash复制# 处理每个CSV文件(最大重试3次)
retry=0
until [[ $retry -ge 3 ]] || process_csv "$file"
do
((retry++))
sleep 1
done
不同循环方式的执行时间对比(处理10000次迭代):
| 循环类型 | 时间(秒) | 内存占用 |
|---|---|---|
| for i in | 0.12 | 1.2MB |
| for ((i=1;i<=10000;i++)) | 0.08 | 1.1MB |
| while [ $i -le 10000 ] | 0.15 | 1.3MB |
| seq 1 10000 | xargs -n1 | 0.05 |
结论:对性能敏感的场景推荐使用C风格for循环或xargs并行处理
bash复制# 设置超时机制
timeout=60
start=$SECONDS
while [ $((SECONDS-start)) -lt $timeout ] && ! check_condition
do
sleep 5
done
bash复制while IFS= read -r -d '' file; do
echo "Processing: $file"
done < <(find . -name "*.log" -print0)
bash复制#!/bin/bash
# 定义备份目录和保留天数
backup_dir="/backups"
keep_days=7
timestamp=$(date +%Y%m%d_%H%M%S)
# 创建当日备份目录
mkdir -p "$backup_dir/$timestamp" || exit 1
# 备份关键目录
for dir in /etc /home /var/www; do
base_name=$(basename "$dir")
tar -czf "$backup_dir/$timestamp/${base_name}.tgz" "$dir"
done
# 清理旧备份
find "$backup_dir" -type d -mtime +$keep_days -exec rm -rf {} \;
bash复制#!/bin/bash
devices=("192.168.1.1" "192.168.1.2" "192.168.1.3")
timeout=2
attempts=3
while true; do
clear
echo "Network Device Monitoring - $(date)"
echo "----------------------------------"
for ip in "${devices[@]}"; do
success=0
for ((i=1; i<=attempts; i++)); do
if ping -c 1 -W $timeout "$ip" &>/dev/null; then
((success++))
fi
done
if [ $success -eq 0 ]; then
status="\033[31mDOWN\033[0m"
elif [ $success -eq $attempts ]; then
status="\033[32mUP\033[0m"
else
status="\033[33mUNSTABLE\033[0m"
fi
printf "%-15s %b\n" "$ip" "$status"
done
sleep 60
done
使用GNU parallel实现并行:
bash复制# 基本并行处理
for i in {1..10}; do
echo "Processing $i"
done | parallel -j 4
# 保持输出顺序
seq 1 10 | parallel -k -j 4 "echo Processing {}"
Bash 4.0+支持关联数组:
bash复制declare -A servers=(
[web1]="192.168.1.10"
[db1]="192.168.1.20"
[cache1]="192.168.1.30"
)
for server in "${!servers[@]}"; do
ip="${servers[$server]}"
echo "$server -> $ip"
done
bash复制process_item() {
local item=$1
echo "Processing $item"
# 复杂处理逻辑...
}
# 主循环
while read -r item; do
process_item "$item" &
((count++))
[[ $((count % 10)) -eq 0 ]] && wait # 每10个并行一次
done < item_list.txt
wait # 等待所有后台任务完成
bash复制# 数值循环兼容写法
max=10
i=1
while [ $i -le $max ]; do
echo $i
i=$((i+1))
done
# 文件处理兼容写法
find . -type f | while IFS= read -r file; do
echo "$file"
done
bash复制# 检测Bash版本
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
# 使用高级特性
for i in {1..10..2}; do echo $i; done
else
# 回退方案
for i in $(seq 1 2 10); do echo $i; done
fi
[[ ]])编写/bin/sh脚本$(( ))而非let或exprwhile IFS= read -r模式