Shell脚本循环结构#

循环结构是Shell脚本中处理重复任务的核心机制,允许脚本根据条件重复执行一系列命令。本教程将详细介绍Shell中的各种循环结构,从基础的for循环到复杂的嵌套循环和循环控制。

入门#

基本for循环#

for循环是Shell中最常用的循环结构之一,用于遍历列表中的元素。

# 基本for循环
for variable in item1 item2 item3; do
    # 执行命令
    echo $variable
done

# 示例:遍历数字
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

# 示例:遍历字符串
for fruit in apple banana cherry; do
    echo "水果: $fruit"
done

# 示例:遍历命令输出
for file in $(ls *.txt); do
    echo "文件: $file"
done

基本while循环#

while循环会在条件为真时重复执行命令,直到条件变为假。

# 基本while循环
while [ condition ]; do
    # 执行命令
done

# 示例:计数器循环
count=1
while [ $count -le 5 ]; do
    echo "计数: $count"
    count=$((count + 1))
done

# 示例:读取文件内容
while read line; do
    echo "行: $line"
done < file.txt

# 示例:无限循环
while true; do
    echo "按Ctrl+C退出"
    sleep 1
done

中级#

高级for循环#

C风格for循环#

# C风格for循环
for ((i=1; i<=5; i++)); do
    echo "数字: $i"
done

# 带步长的循环
for ((i=1; i<=10; i+=2)); do
    echo "奇数: $i"
done

# 反向循环
for ((i=5; i>=1; i--)); do
    echo "倒计时: $i"
done

通配符和序列#

# 使用通配符
for file in *.txt; do
    echo "文本文件: $file"
done

# 使用序列表达式
for i in {1..5}; do
    echo "数字: $i"
done

# 带步长的序列
for i in {1..10..2}; do
    echo "奇数: $i"
done

# 字母序列
for letter in {a..e}; do
    echo "字母: $letter"
done

until循环#

until循环与while循环相反,会在条件为假时重复执行命令,直到条件变为真。

# 基本until循环
until [ condition ]; do
    # 执行命令
done

# 示例:计数器循环
count=1
until [ $count -gt 5 ]; do
    echo "计数: $count"
    count=$((count + 1))
done

# 示例:等待文件创建
until [ -f "flag.txt" ]; do
    echo "等待flag.txt创建..."
    sleep 1
done
echo "flag.txt已创建"

高级#

循环控制#

break命令#

break命令用于跳出当前循环,终止循环的执行。

# 基本break
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break  # 跳出循环
    fi
    echo "数字: $i"
done

# 跳出嵌套循环
for i in {1..3}; do
    for j in {1..3}; do
        if [ $j -eq 2 ]; then
            break  # 只跳出内层循环
        fi
        echo "i=$i, j=$j"
    done
done

# 跳出指定层数的循环
for i in {1..3}; do
    for j in {1..3}; do
        for k in {1..3}; do
            if [ $k -eq 2 ]; then
                break 3  # 跳出所有三层循环
            fi
            echo "i=$i, j=$j, k=$k"
        done
    done
done

continue命令#

continue命令用于跳过当前循环的剩余部分,直接开始下一次循环。

# 基本continue
for i in {1..5}; do
    if [ $i -eq 3 ]; then
        continue  # 跳过当前迭代
    fi
    echo "数字: $i"
done

# 跳过奇数
for i in {1..10}; do
    if [ $((i % 2)) -eq 1 ]; then
        continue
    fi
    echo "偶数: $i"
done

# 跳过空文件
for file in *.txt; do
    if [ ! -s "$file" ]; then
        continue
    fi
    echo "非空文件: $file"
done

return和exit命令#

# 在函数中使用return
my_function() {
    for i in {1..5}; do
        if [ $i -eq 3 ]; then
            return  # 从函数返回
        fi
        echo "函数内: $i"
    done
}

my_function

# 使用exit退出脚本
for i in {1..5}; do
    if [ $i -eq 3 ]; then
        echo "退出脚本"
        exit  # 退出整个脚本
    fi
    echo "脚本内: $i"
done

嵌套循环#

# 基本嵌套循环
for i in {1..3}; do
    for j in {1..3}; do
        echo "i=$i, j=$j"
    done
done

# 九九乘法表
for i in {1..9}; do
    for j in {1..9}; do
        if [ $j -le $i ]; then
            result=$((i * j))
            echo -n "$i*$j=$result "
        fi
    done
    echo  # 换行
done

# 目录和文件遍历
for dir in */; do
    echo "目录: $dir"
    for file in "$dir"*.txt; do
        if [ -f "$file" ]; then
            echo "  文件: $(basename $file)"
        fi
    done
done

大师#

高级循环技巧#

进程替换循环#

# 使用进程替换
while read line; do
    echo "处理: $line"
done < <(ls -la)

# 同时读取多个文件
while read -u 3 line1 && read -u 4 line2; do
    echo "文件1: $line1"
    echo "文件2: $line2"
done 3< file1.txt 4< file2.txt

数组遍历#

# 遍历数组
fruits=(apple banana cherry date)
for fruit in "${fruits[@]}"; do
    echo "水果: $fruit"
done

# 使用索引遍历数组
for ((i=0; i<${#fruits[@]}; i++)); do
    echo "索引$i: ${fruits[$i]}"
done

# 关联数组遍历
declare -A colors
colors["red"]="#FF0000"
colors["green"]="#00FF00"
colors["blue"]="#0000FF"

for color in "${!colors[@]}"; do
    echo "$color: ${colors[$color]}"
done

并行循环#

# 使用后台进程并行执行
for i in {1..5}; do
    (echo "任务$i开始"; sleep 2; echo "任务$i完成") &
done
wait  # 等待所有后台进程完成

echo "所有任务完成"

# 限制并行数量
max_jobs=3
current_jobs=0

for i in {1..10}; do
    (echo "任务$i开始"; sleep 1; echo "任务$i完成") &
    current_jobs=$((current_jobs + 1))
    
    if [ $current_jobs -ge $max_jobs ]; then
        wait  # 等待所有后台进程完成
        current_jobs=0
    fi
done
wait

echo "所有任务完成"

性能优化#

# 减少循环内的命令执行
# 不好的做法
for i in {1..1000}; do
    echo "数字: $i"
done

# 好的做法(使用缓冲区)
buffer=""
for i in {1..1000}; do
    buffer+="数字: $i\n"
done
echo "$buffer"

# 避免循环内的文件I/O
# 不好的做法
for i in {1..1000}; do
    echo "数字: $i" >> output.txt
done

# 好的做法
{ for i in {1..1000}; do
    echo "数字: $i"
done } > output.txt

# 使用更快的循环结构
# C风格for循环比传统for循环快
for ((i=1; i<=1000000; i++)); do
    :  # 空命令
done

# 避免子shell
# 不好的做法(每次循环创建子shell)
for i in $(seq 1 1000); do
    echo $i
done

# 好的做法
for i in {1..1000}; do
    echo $i
done

无敌#

高级循环应用#

事件驱动循环#

# 事件驱动循环
while true; do
    read -t 1 -n 1 key
    
    if [ $? -eq 0 ]; then
        case $key in
            q)
                echo "退出"
                exit 0
                ;;
            *)
                echo "按下: $key"
                ;;
        esac
    fi
    
    # 其他处理代码
    echo -n "."
done

状态机循环#

# 简单状态机
state="idle"

while true; do
    case $state in
        idle)
            echo "等待输入..."
            read command
            case $command in
                start)
                    state="running"
                    ;;
                quit)
                    state="exiting"
                    ;;
            esac
            ;;
        running)
            echo "运行中..."
            sleep 1
            # 检查是否需要切换状态
            if [ -f "stop.txt" ]; then
                state="idle"
                rm stop.txt
            fi
            ;;
        exiting)
            echo "退出中..."
            exit 0
            ;;
    esac
done

实用脚本示例#

批量文件处理脚本#

#!/bin/bash

# 批量重命名文件
echo "批量重命名.txt文件为.bak文件"

for file in *.txt; do
    if [ -f "$file" ]; then
        new_name="${file%.txt}.bak"
        echo "重命名: $file -> $new_name"
        mv "$file" "$new_name"
    fi
done

echo "重命名完成"

系统监控脚本#

#!/bin/bash

# 系统资源监控脚本
interval=5  # 监控间隔(秒)
count=10    # 监控次数

for ((i=1; i<=count; i++)); do
    echo "===== 监控第$i次 ====="
    
    # 查看CPU使用率
    echo "CPU使用率:"
    top -bn1 | grep "Cpu(s)"
    
    # 查看内存使用
    echo "内存使用:"
    free -h
    
    # 查看磁盘使用
    echo "磁盘使用:"
    df -h
    
    # 查看网络连接
    echo "网络连接数:"
    netstat -tuln | wc -l
    
    if [ $i -lt $count ]; then
        echo "等待${interval}秒..."
        sleep $interval
    fi
done

echo "监控完成"

日志分析脚本#

#!/bin/bash

# 日志分析脚本
log_file="/var/log/syslog"

# 统计不同级别的日志
levels=()

while read line; do
    # 提取日志级别
    level=$(echo $line | grep -o "[A-Z]\{3,5\}" | head -n 1)
    if [ -n "$level" ]; then
        levels+=("$level")
    fi
done < "$log_file"

# 统计并排序
echo "日志级别统计:"
printf "%s\n" "${levels[@]}" | sort | uniq -c | sort -nr

# 统计错误数量
error_count=$(grep -c "ERROR" "$log_file")
echo "错误数量: $error_count"

# 显示最近的10条错误
echo "最近的10条错误:"
grep "ERROR" "$log_file" | tail -n 10

总结#

循环结构是Shell脚本中处理重复任务的强大工具,通过掌握forwhileuntil等循环结构以及breakcontinue等循环控制语句,您可以创建更加灵活和高效的Shell脚本。从简单的文件遍历到复杂的系统监控,循环结构在各种场景中都发挥着重要作用。

随着您对Shell循环结构的理解不断深入,您将能够编写更加健壮、高效的Shell脚本,处理各种复杂的任务和场景。记住,合理使用循环结构可以大大提高脚本的自动化程度和执行效率。