Shell编程从入门到精通-第五章 流程控制

流程控制是Shell脚本实现复杂逻辑的核心。本章将详细介绍Shell中的条件语句(if-elif-else)、多分支语句(case)以及各种循环结构(for、while、until),同时深入讲解循环的高级用法、信号处理和超时控制等实用场景,帮助你全面掌握Shell脚本的流程控制能力。


5.1 if-elif-else条件语句

5.1.1 基本语法

Shell中的if语句使用fi作为结束标记,语法结构如下:

bash 复制代码
# 单分支if
if 条件; then
    命令
fi

# 双分支if-else
if 条件; then
    命令1
else
    命令2
fi

# 多分支if-elif-else
if 条件1; then
    命令1
elif 条件2; then
    命令2
elif 条件3; then
    命令3
else
    命令4
fi

5.1.2 条件表达式写法

bash 复制代码
#!/bin/bash

# 使用 [ ] 进行条件判断
name="zhangsan"
if [ "$name" = "zhangsan" ]; then
    echo "名字匹配"
fi

# 使用 [[ ]] 进行条件判断(推荐,支持模式匹配)
name="zhangsan"
if [[ $name == zhang* ]]; then
    echo "名字以zhang开头"
fi

# 使用 (( )) 进行数学比较
a=10
b=20
if (( a < b )); then
    echo "a小于b"
fi

# 结合多个条件
age=25
if [ $age -ge 18 ] && [ $age -le 60 ]; then
    echo "年龄在18到60之间"
fi

# 使用 -a 和 -o(在 [ ] 中)
if [ $age -ge 18 -a $age -le 60 ]; then
    echo "年龄在18到60之间"
fi

# 在 [[ ]] 中使用 && 和 ||
if [[ $age -ge 18 && $age -le 60 ]]; then
    echo "年龄在18到60之间"
fi

5.1.3 嵌套if语句与多层条件

bash 复制代码
#!/bin/bash

# 嵌套if语句
num=75

if [ $num -ge 60 ]; then
    if [ $num -ge 90 ]; then
        echo "优秀"
    elif [ $num -ge 80 ]; then
        echo "良好"
    else
        echo "及格"
    fi
else
    echo "不及格"
fi

# 使用 && 和 || 简化嵌套if
# 等价于上面的嵌套if
[[ $num -ge 90 ]] && echo "优秀" && exit
[[ $num -ge 80 ]] && echo "良好" && exit
[[ $num -ge 60 ]] && echo "及格" || echo "不及格"

5.1.4 if语句的退出状态

if语句实际上是根据命令的退出状态来决定分支执行的:

bash 复制代码
#!/bin/bash

# if 后跟命令(而非条件表达式)
if grep -q "error" /var/log/syslog 2>/dev/null; then
    echo "发现错误"
fi

# if后跟函数返回值
check_service() {
    systemctl is-active nginx >/dev/null 2>&1
}

if check_service; then
    echo "Nginx服务正在运行"
else
    echo "Nginx服务未运行"
fi

# 使用命令的退出状态进行判断
if ! command -v git >/dev/null 2>&1; then
    echo "git未安装"
fi

5.2 case多分支语句

5.2.1 基本语法

case语句类似于其他语言中的switch-case,用于多分支选择:

bash 复制代码
case 变量 in
    模式1)
        命令1
        ;;  # 类似于break
    模式2)
        命令2
        ;;
    模式3)
        命令3
        ;;
    *)  # 默认分支,类似于default
        命令4
        ;;
esac  # case的反写

5.2.2 使用通配符模式

bash 复制代码
#!/bin/bash

# 使用通配符
echo "请输入一个字符:"
read char

case $char in
    [a-z])
        echo "你输入了小写字母"
        ;;
    [A-Z])
        echo "你输入了大写字母"
        ;;
    [0-9])
        echo "你输入了数字"
        ;;
    *)
        echo "你输入了其他字符"
        ;;
esac

5.2.3 多个模式匹配

bash 复制代码
#!/bin/bash

# 多个模式用 | 分隔
echo "请输入月份(1-12):"
read month

case $month in
    1|2|3)
        echo "春天"
        ;;
    4|5|6)
        echo "夏天"
        ;;
    7|8|9)
        echo "秋天"
        ;;
    10|11|12)
        echo "冬天"
        ;;
    *)
        echo "无效的月份"
        ;;
esac

5.2.4 case实战应用

bash 复制代码
#!/bin/bash

# 1. 系统服务管理
service_action() {
    local action=$1
    local service=$2
    
    case $action in
        start)
            echo "启动 $service"
            ;;
        stop)
            echo "停止 $service"
            ;;
        restart)
            echo "重启 $service"
            ;;
        status)
            echo "查看 $service 状态"
            ;;
        *)
            echo "未知操作: $action"
            return 1
            ;;
    esac
}

# 2. 文件处理
process_file() {
    local filename=$1
    local ext="${filename##*.}"
    
    case $ext in
        txt)
            echo "处理文本文件"
            ;;
        jpg|jpeg|png|gif)
            echo "处理图片文件"
            ;;
        mp4|avi|mkv)
            echo "处理视频文件"
            ;;
        sh)
            echo "处理Shell脚本"
            ;;
        *)
            echo "未知文件类型"
            ;;
    esac
}

# 3. 菜单驱动
show_menu() {
    echo "1. 显示日期"
    echo "2. 显示日历"
    echo "3. 显示当前目录"
    echo "4. 退出"
}

while true; do
    show_menu
    read -p "请选择: " choice
    
    case $choice in
        1) date ;;
        2) cal ;;
        3) pwd ;;
        4) echo "再见"; break ;;
        *) echo "无效选择" ;;
    esac
done

5.3 for循环

5.3.1 列表for循环

遍历一系列值:

bash 复制代码
#!/bin/bash

# 基本列表for循环
for i in 1 2 3 4 5; do
    echo "计数器: $i"
done

# 使用花括号生成序列
for i in {1..5}; do
    echo "计数器: $i"
done

# 指定步长(使用seq)
for i in $(seq 1 2 10); do
    echo "奇数: $i"
done

# 负数步长
for i in $(seq 10 -2 1); do
    echo "倒序偶数: $i"
done

5.3.2 C风格for循环

类似C语言的for循环语法:

bash 复制代码
#!/bin/bash

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

# 指定步长
for ((i=0; i<=10; i+=2)); do
    echo "偶数: $i"
done

# 多个变量
for ((i=1, j=10; i<=5; i++, j--)); do
    echo "i=$i, j=$j"
done

# 复杂条件
for ((i=1; i<=100 && j>0; i++)); do
    # 循环体
    ((j--))
done

5.3.3 遍历文件和目录

bash 复制代码
#!/bin/bash

# 遍历当前目录下的所有文件
for file in *; do
    echo "文件: $file"
done

# 遍历特定类型的文件
for file in *.md; do
    echo "Markdown文件: $file"
done

# 递归遍历(使用find)
for file in $(find . -type f -name "*.sh"); do
    echo "Shell脚本: $file"
done

# 遍历目录
for dir in */; do
    echo "目录: $dir"
done

5.3.4 for循环高级用法

bash 复制代码
#!/bin/bash

# 1. 并行处理(使用&和wait)
echo "=== 并行处理示例 ==="
for i in {1..5}; do
    sleep 1 &
done
wait
echo "所有任务完成"

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

# 3. 遍历关联数组
declare -A capitals=([China]="Beijing" [Japan]="Tokyo")
for country in "${!capitals[@]}"; do
    echo "$country 的首都是 ${capitals[$country]}"
done

# 4. 读取文件每行(避免空格问题)
while IFS= read -r line; do
    echo "行: $line"
done < "filename.txt"

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

5.4 while/until循环

5.4.1 while循环

条件为真时循环:

bash 复制代码
#!/bin/bash

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

# 读取文件行(逐行处理)
while read line; do
    echo "行内容: $line"
done < /etc/hostname

# 无限循环(配合break使用)
count=1
while true; do
    echo "无限循环计数: $count"
    if [ $count -ge 3 ]; then
        break
    fi
    count=$((count + 1))
done

# 逐行读取文件并处理
while IFS= read -r line || [ -n "$line" ]; do
    echo "$line"
done < "file.txt"

5.4.2 until循环

条件为假时循环(直到条件为真):

bash 复制代码
#!/bin/bash

# 基本until循环
# 直到count大于5才停止
count=1
until [ $count -gt 5 ]; do
    echo "计数: $count"
    count=$((count + 1))
done

# while和until对比
echo "=== while循环 ==="
i=1
while [ $i -le 3 ]; do
    echo "i = $i"
    i=$((i + 1))
done

echo "=== until循环 ==="
i=1
until [ $i -gt 3 ]; do
    echo "i = $i"
    i=$((i + 1))
done

5.4.3 计数器与累加器

bash 复制代码
#!/bin/bash

# 数字累加计算
sum=0
i=1
while [ $i -le 100 ]; do
    sum=$((sum + i))
    i=$((i + 1))
done
echo "1+2+...+100 = $sum"

# 读取多个数字并求和
sum=0
while true; do
    read -p "请输入数字(0结束): " num
    [ "$num" = "0" ] && break
    sum=$((sum + num))
    echo "当前总和: $sum"
done
echo "最终结果: $sum"

# 计算阶乘
factorial=1
num=5
while [ $num -gt 1 ]; do
    factorial=$((factorial * num))
    num=$((num - 1))
done
echo "5! = $factorial"

5.5 循环控制:break与continue

5.5.1 break语句

立即退出循环:

bash 复制代码
#!/bin/bash

# 基本break用法
for i in 1 2 3 4 5; do
    if [ $i -eq 3 ]; then
        echo "遇到3,退出循环"
        break
    fi
    echo "当前数字: $i"
done
echo "循环结束"

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

5.5.2 continue语句

跳过当前迭代,继续下一次循环:

bash 复制代码
#!/bin/bash

# 基本continue用法
for i in 1 2 3 4 5; do
    if [ $i -eq 3 ]; then
        echo "跳过3"
        continue
    fi
    echo "当前数字: $i"
done

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

5.5.3 break与continue的高级用法

bash 复制代码
#!/bin/bash

# break指定退出层级
for i in 1 2 3; do
    for j in 1 2 3; do
        if [ $i -eq 2 ] && [ $j -eq 2 ]; then
            break 2  # 退出两层循环
        fi
        echo "i=$i, j=$j"
    done
done

# continue跳到指定层级
for i in {1..3}; do
    for j in {1..3}; do
        if [ $j -eq 2 ]; then
            continue  # 跳到下一次内层循环
        fi
        echo "i=$i, j=$j"
    done
done

5.6 高级循环技巧

5.6.1 循环中的超时控制

bash 复制代码
#!/bin/bash

# 使用timeout命令
echo "=== 超时控制示例 ==="
timeout 3 bash -c 'while true; do echo "运行中"; sleep 1; done'

# 使用read超时
echo "请在3秒内输入:"
if read -t 3 -r response; then
    echo "你输入了: $response"
else
    echo "超时未输入"
fi

# 循环中的超时处理
counter=0
while true; do
    sleep 1
    counter=$((counter + 1))
    if [ $counter -ge 5 ]; then
        echo "达到最大循环次数"
        break
    fi
done

5.6.2 循环中的信号处理

bash 复制代码
#!/bin/bash

# 捕获Ctrl+C信号
trap 'echo "收到中断信号,退出循环"; break' INT

# 或使用EXIT清理
cleanup() {
    echo "清理资源..."
}
trap cleanup EXIT

count=0
while true; do
    echo "计数: $count"
    sleep 1
    count=$((count + 1))
done

# 忽略信号
trap '' INT  # 忽略Ctrl+C
echo "按Ctrl+C无效"
sleep 10
trap INT  # 恢复信号处理

5.6.3 进度显示

bash 复制代码
#!/bin/bash

# 简单进度条
progress_bar() {
    local current=$1
    local total=$2
    local width=50
    
    percent=$((current * 100 / total))
    completed=$((width * current / total))
    remaining=$((width - completed))
    
    printf "\r进度: ["
    printf "%${completed}s" | tr ' ' '='
    printf "%${remaining}s" | tr ' ' '-'
    printf "] %3d%%" $percent
    
    [ $current -eq $total ] && echo ""
}

# 使用进度条
total=20
for i in $(seq 1 $total); do
    sleep 0.2
    progress_bar $i $total
done

# 旋转指针
spinner() {
    local pid=$1
    local delay=0.1
    local spinstr='|/-\'
    
    while ps -p $pid > /dev/null 2>&1; do
        local temp=${spinstr#?}
        printf "\r[%c] " "$spinstr"
        local spinstr=$temp${spinstr%"$temp"}
        sleep $delay
    done
    printf "\r"
}

# 后台任务配合旋转指针
sleep 10 &
spinner $!
echo "任务完成"

5.6.4 循环中的错误处理

bash 复制代码
#!/bin/bash

# 遇到错误继续执行
for file in file1.txt file2.txt nonexistent.txt; do
    if [ -f "$file" ]; then
        echo "处理 $file"
    else
        echo "警告: $file 不存在,跳过" >&2
        continue
    fi
done

# 遇到错误退出
set -e  # 遇到错误立即退出
for i in {1..5}; do
    echo "处理 $i"
    if [ $i -eq 3 ]; then
        exit 1
    fi
done

# 错误计数
error_count=0
for i in {1..10}; do
    if [ $((i % 3)) -eq 0 ]; then
        echo "错误发生在 $i" >&2
        error_count=$((error_count + 1))
    else
        echo "处理 $i 成功"
    fi
done

echo "共发生 $error_count 个错误"

5.7 综合示例

5.7.1 菜单驱动的系统管理脚本

bash 复制代码
#!/bin/bash

show_menu() {
    echo "================================"
    echo "      系统管理脚本"
    echo "================================"
    echo "1. 查看系统信息"
    echo "2. 查看磁盘使用"
    echo "3. 查看内存使用"
    echo "4. 查看进程"
    echo "5. 查看网络连接"
    echo "6. 批量文件操作"
    echo "0. 退出"
    echo "================================"
}

show_system_info() {
    echo "=== 系统信息 ==="
    echo "主机名: $(hostname)"
    echo "内核版本: $(uname -r)"
    echo "运行时间: $(uptime -p)"
    echo "负载: $(cat /proc/loadavg | awk '{print $1, $2, $3}')"
}

show_disk_usage() {
    echo "=== 磁盘使用情况 ==="
    df -h | grep -v tmpfs
}

show_memory() {
    echo "=== 内存使用情况 ==="
    free -h
}

show_processes() {
    echo "=== 运行中的进程 ==="
    ps aux --sort=-%mem | head -n 11
}

show_network() {
    echo "=== 网络连接 ==="
    netstat -tun | tail -n +3
}

batch_file_operation() {
    echo "=== 批量文件操作 ==="
    echo "请选择操作1. :"
    echo "批量重命名"
    echo "2. 批量压缩"
    echo "3. 批量复制"
    read -p "选择: " choice
    
    case $choice in
        1)
            read -p "输入文件前缀: " prefix
            for f in *; do
                [ -f "$f" ] && mv "$f" "${prefix}$f"
            done
            ;;
        2)
            tar -czf backup.tar.gz *
            ;;
        3)
            read -p "目标目录: " dest
            cp -r * "$dest"
            ;;
        *)
            echo "无效选择"
            ;;
    esac
}

# 主循环
while true; do
    show_menu
    read -p "请选择 [0-6]: " choice
    
    case $choice in
        1) show_system_info ;;
        2) show_disk_usage ;;
        3) show_memory ;;
        4) show_processes ;;
        5) show_network ;;
        6) batch_file_operation ;;
        0) echo "感谢使用,再见!"; exit 0 ;;
        *) echo "无效选择,请重新输入" ;;
    esac
    
    echo ""
    read -p "按回车继续..."
done

5.7.2 批量处理工具

bash 复制代码
#!/bin/bash

# 批量处理图片文件

# 创建测试目录
mkdir -p /tmp/image_process
cd /tmp/image_process

# 创建测试文件
touch photo1.jpg photo2.jpg photo3.png photo4.jpg screenshot.png

echo "=== 原始文件 ==="
ls -la

echo ""
echo "=== 开始处理 ==="

count=0
skipped=0
errors=0

for file in *; do
    # 跳过目录
    [ -d "$file" ] && continue
    
    # 获取文件扩展名
    ext="${file##*.}"
    
    case $ext in
        jpg|jpeg)
            echo "[处理] $file -> JPEG图片"
            # 这里可以添加实际的图片处理命令
            count=$((count + 1))
            ;;
        png)
            echo "[处理] $file -> PNG图片"
            count=$((count + 1))
            ;;
        gif)
            echo "[处理] $file -> GIF图片"
            count=$((count + 1))
            ;;
        *)
            echo "[跳过] $file -> 未知格式"
            skipped=$((skipped + 1))
            ;;
    esac
done

echo ""
echo "=== 处理完成 ==="
echo "共处理 $count 个图片文件"
echo "跳过 $skipped 个文件"

5.7.3 猜数字游戏

bash 复制代码
#!/bin/bash

# 猜数字游戏

# 生成1-100的随机数
target=$((RANDOM % 100 + 1))
attempts=0
max_attempts=7

echo "================================"
echo "    猜数字游戏"
echo "================================"
echo "我已经想好了一个1-100之间的数字"
echo "你最多可以猜 $max_attempts 次"
echo "================================"

while true; do
    read -p "请输入你的猜测: " guess
    
    # 验证输入
    if ! [[ $guess =~ ^[0-9]+$ ]]; then
        echo "请输入有效的数字!"
        continue
    fi
    
    # 检查范围
    if [ $guess -lt 1 ] || [ $guess -gt 100 ]; then
        echo "请输入1-100之间的数字!"
        continue
    fi
    
    attempts=$((attempts + 1))
    
    # 判断猜测结果
    diff=$((guess - target))
    diff=${diff#-}  # 取绝对值
    
    if [ $guess -lt $target ]; then
        if [ $diff -le 10 ]; then
            echo "很接近了!再大一点"
        else
            echo "太小了!再试试"
        fi
    elif [ $guess -gt $target ]; then
        if [ $diff -le 10 ]; then
            echo "很接近了!再小一点"
        else
            echo "太大了!再试试"
        fi
    else
        echo "================================"
        echo "恭喜你!猜对了!"
        echo "正确答案是 $target"
        echo "你一共猜了 $attempts 次"
        
        # 评价
        if [ $attempts -le 3 ]; then
            echo "太厉害了!你是高手!"
        elif [ $attempts -le 5 ]; then
            echo "很不错!继续加油!"
        else
            echo "运气不错!"
        fi
        echo "================================"
        break
    fi
    
    # 检查剩余次数
    remaining=$((max_attempts - attempts))
    if [ $remaining -le 0 ]; then
        echo "================================"
        echo "游戏结束!"
        echo "正确答案是 $target"
        echo "你已经猜了 $attempts 次"
        echo "================================"
        break
    else
        echo "剩余次数: $remaining"
    fi
done

5.7.4 数据处理脚本

bash 复制代码
#!/bin/bash

# 数据处理脚本 - 处理CSV文件

# 假设CSV文件格式: 姓名,年龄,城市,职业
CSV_FILE="data.csv"

# 创建测试数据
mkdir -p /tmp/csv_process
cat > /tmp/csv_process/data.csv << 'EOF'
张三,25,北京,工程师
李四,30,上海,设计师
王五,28,广州,产品经理
赵六,35,深圳,CEO
孙七,22,杭州,实习生
周八,40,北京,总监
EOF

cd /tmp/csv_process

echo "=== 原始数据 ==="
cat data.csv

echo ""
echo "=== 数据统计 ==="
total_lines=$(wc -l < data.csv)
echo "总记录数: $total_lines"

echo ""
echo "=== 按城市统计 ==="
cut -d',' -f3 data.csv | sort | uniq -c | while read count city; do
    echo "$city: $count 人"
done

echo ""
echo "=== 年龄分析 ==="
ages=$(cut -d',' -f2 data.csv)
avg_age=$(echo "$ages" | awk '{sum+=$1} END {print int(sum/NR)}')
max_age=$(echo "$ages" | sort -n | tail -1)
min_age=$(echo "$ages" | sort -n | head -1)

echo "平均年龄: $avg_age"
echo "最大年龄: $max_age"
echo "最小年龄: $min_age"

echo ""
echo "=== 年龄段分布 ==="
for i in {20..40..10}; do
    next=$((i + 9))
    count=$(awk -F',' -v start=$i -v end=$next '$2>=start && $2<end' data.csv | wc -l)
    [ $count -gt 0 ] && echo "$i-$next岁: $count 人"
done

echo ""
echo "=== 数据处理完成 ==="

5.8 本章小结

本章详细介绍了Shell脚本中的流程控制语法:

  • if-elif-else条件语句

    • 单分支、双分支、多分支if语句
    • 条件表达式的写法([ ][[ ]](( ))
    • 嵌套if语句与多层条件
    • if语句的退出状态
  • case多分支语句

    • 基本语法和结束标记(esac
    • 模式匹配(通配符、|分隔多个模式)
    • 实战应用(服务管理、文件处理、菜单驱动)
  • for循环

    • 列表for循环(遍历值、序列、文件)
    • C风格for循环
    • 遍历文件和命令输出
    • 并行处理、遍历数组和关联数组
  • while/until循环

    • while循环(条件为真循环)
    • until循环(条件为假循环)
    • 读取文件内容
    • 计数器与累加器
  • 循环控制

    • break:立即退出循环
    • continue:跳过当前迭代
    • break n:退出多层循环
  • 高级循环技巧

    • 超时控制
    • 信号处理
    • 进度显示
    • 错误处理

掌握流程控制语法后,你将能够编写具有复杂逻辑判断和循环处理的Shell脚本,实现各种自动化任务。

更多内容,欢迎访问南徽玉的个人博客

相关推荐
Nanhuiyu3 小时前
Shell编程从入门到精通-第七章 文本三剑客
shell编程
Nanhuiyu6 小时前
Shell编程从入门到精通-第六章 函数
shell编程
Nanhuiyu1 天前
Shell编程从入门到精通-第四章 条件判断
shell编程
Nanhuiyu2 天前
Shell编程从入门到精通-第一章 Shell简介与环境配置
shell编程
予枫的编程笔记12 天前
【Linux进阶篇】从基础到实战:grep高亮、sed流编辑、awk分析,全场景覆盖
linux·sed·grep·awk·shell编程·文本处理三剑客·管道命令
vortex512 天前
解密UUOC:Shell编程中“无用的cat使用”详解
linux·shell编程
燃于AC之乐17 天前
【Linux系统编程】Shell解释器完全实现:从命令解析、环境变量管理到内建命令的全面解析
linux·操作系统·命令行工具·进程控制·shell编程
PyHaVolask1 个月前
Linux零基础入门:输入输出重定向完全指南
linux运维·shell编程·输入输出流·linux重定向·命令行技巧
牛奶咖啡131 个月前
shell脚本编程(二)
linux·正则表达式·shell编程·正则表达式扩展·shell通配符·shell的变量·shell的引用