Shell 流程控制

Shell流程控制详解

掌握Shell脚本中的条件判断和循环控制

本教程使用Bootstrap 5构建,详细介绍Shell脚本中的流程控制结构,包括if语句、case语句、for循环、while循环等。

Shell流程控制概述

流程控制是编程语言中的核心概念,它允许程序根据条件执行不同的代码块,或者重复执行某些代码块。

在Shell脚本中,流程控制主要包括两大类:

  • 条件判断 - 根据条件决定执行哪部分代码
  • 循环控制 - 重复执行某段代码直到满足特定条件
学习提示

Shell脚本中的流程控制结构与大多数编程语言类似,但语法有些不同。掌握这些结构是编写高效Shell脚本的关键。

流程控制结构概览
条件判断
if语句
case语句

循环控制
for循环
while循环
until循环

每种结构都有其特定的使用场景和语法规则,下面将详细介绍。

条件判断

if语句

if语句用于根据条件执行不同的代码块。在Shell中,if语句的基本语法如下:

基本语法
if [ 条件 ]
then
    # 条件为真时执行的代码
elif [ 其他条件 ]
then
    # 其他条件为真时执行的代码
else
    # 所有条件都为假时执行的代码
fi
if语句的特点
  • 使用if开始,以fi结束
  • 条件测试使用方括号[ ][[ ]]
  • 可以使用elif添加多个条件分支
  • else分支是可选的
示例1: 简单的if语句
#!/bin/bash

# 检查文件是否存在
if [ -f "myfile.txt" ]
then
    echo "文件存在"
else
    echo "文件不存在"
fi
示例2: 使用elif的多条件判断
#!/bin/bash

# 根据分数判断等级
score=85

if [ $score -ge 90 ]
then
    echo "优秀"
elif [ $score -ge 80 ]
then
    echo "良好"
elif [ $score -ge 70 ]
then
    echo "中等"
elif [ $score -ge 60 ]
then
    echo "及格"
else
    echo "不及格"
fi
提示: 在Shell中,条件测试时方括号内需要留空格,如[ $var -eq 10 ]

case语句

case语句用于基于模式匹配的多分支选择,比多个if-elif语句更简洁。

基本语法
case 变量 in
    模式1)
        # 匹配模式1时执行的代码
        ;;
    模式2)
        # 匹配模式2时执行的代码
        ;;
    *)
        # 默认情况执行的代码
        ;;
esac
case语句的特点
  • 使用case开始,以esac结束
  • 每个分支以)开始,以;;结束
  • 可以使用通配符进行模式匹配
  • *表示默认情况
示例1: 简单的case语句
#!/bin/bash

# 根据用户输入执行不同操作
echo "请输入操作(start|stop|restart|status):"
read operation

case $operation in
    start)
        echo "启动服务..."
        ;;
    stop)
        echo "停止服务..."
        ;;
    restart)
        echo "重启服务..."
        ;;
    status)
        echo "查看服务状态..."
        ;;
    *)
        echo "未知操作: $operation"
        ;;
esac
示例2: 使用模式匹配
#!/bin/bash

# 根据文件扩展名处理文件
filename="document.pdf"

case $filename in
    *.txt)
        echo "文本文件: $filename"
        ;;
    *.pdf|*.doc|*.docx)
        echo "文档文件: $filename"
        ;;
    *.jpg|*.png|*.gif)
        echo "图像文件: $filename"
        ;;
    *)
        echo "未知文件类型: $filename"
        ;;
esac
提示: case语句中的模式匹配支持通配符,如*匹配任意字符,?匹配单个字符。

条件测试

在Shell中,条件测试用于判断表达式是否为真。可以使用test命令或方括号[ ]

文件测试操作符
操作符 说明
-e file 文件存在
-f file 文件存在且是普通文件
-d file 文件存在且是目录
-r file 文件存在且可读
-w file 文件存在且可写
-x file 文件存在且可执行
字符串测试操作符
操作符 说明
str1 = str2 字符串相等
str1 != str2 字符串不相等
-z str 字符串长度为0
-n str 字符串长度不为0
数值测试操作符
操作符 说明
num1 -eq num2 等于
num1 -ne num2 不等于
num1 -lt num2 小于
num1 -le num2 小于等于
num1 -gt num2 大于
num1 -ge num2 大于等于
条件测试示例
#!/bin/bash

# 文件测试
if [ -f "/etc/passwd" ]; then
    echo "文件存在"
fi

# 字符串测试
name="Alice"
if [ "$name" = "Alice" ]; then
    echo "Hello Alice"
fi

# 数值测试
count=10
if [ $count -gt 5 ]; then
    echo "计数大于5"
fi

循环控制

for循环

for循环用于遍历列表中的每个元素,并对每个元素执行相同的操作。

基本语法
for 变量 in 列表
do
    # 循环体
done
for循环的特点
  • 使用for开始,以done结束
  • 列表可以是显式列表、变量或命令输出
  • C语言风格的for循环在Bash中也支持
  • 常用于遍历文件、数组或命令输出
示例1: 遍历列表
#!/bin/bash

# 遍历显式列表
for fruit in apple banana orange
do
    echo "水果: $fruit"
done
示例2: 遍历文件
#!/bin/bash

# 遍历当前目录下的所有.txt文件
for file in *.txt
do
    echo "处理文件: $file"
    # 对每个文件执行操作
done
示例3: C语言风格的for循环
#!/bin/bash

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

while循环

while循环在条件为真时重复执行代码块。

基本语法
while [ 条件 ]
do
    # 循环体
done
while循环的特点
  • 使用while开始,以done结束
  • 每次循环前检查条件
  • 如果条件一开始就为假,循环体一次也不执行
  • 常用于读取文件或等待某个条件满足
示例1: 计数器
#!/bin/bash

# 使用while循环实现计数器
count=1
while [ $count -le 5 ]
do
    echo "计数: $count"
    count=$((count + 1))
done
示例2: 读取文件内容
#!/bin/bash

# 逐行读取文件
while IFS= read -r line
do
    echo "行内容: $line"
done < "filename.txt"
示例3: 无限循环
#!/bin/bash

# 无限循环(使用true命令)
while true
do
    echo "循环中..."
    sleep 1
done

until循环

until循环在条件为假时重复执行代码块,与while循环相反。

基本语法
until [ 条件 ]
do
    # 循环体
done
until循环的特点
  • 使用until开始,以done结束
  • 每次循环前检查条件
  • 如果条件一开始就为真,循环体一次也不执行
  • 常用于等待某个条件变为真
示例1: 等待服务启动
#!/bin/bash

# 等待服务启动
until systemctl is-active --quiet nginx
do
    echo "等待nginx服务启动..."
    sleep 2
done
echo "nginx服务已启动"
示例2: 计数器
#!/bin/bash

# 使用until循环实现计数器
count=1
until [ $count -gt 5 ]
do
    echo "计数: $count"
    count=$((count + 1))
done
提示: until循环与while循环的主要区别在于条件判断。until循环在条件为假时执行,而while循环在条件为真时执行。

循环控制语句

在循环中,有时需要提前结束循环或跳过某次循环。Shell提供了break和continue语句来实现这些功能。

break语句

break语句用于立即退出循环,不再执行循环中剩余的代码。

break示例
#!/bin/bash

# 在找到特定元素时退出循环
for num in 1 2 3 4 5 6 7 8 9 10
do
    if [ $num -eq 5 ]
    then
        echo "找到数字5,退出循环"
        break
    fi
    echo "当前数字: $num"
done
continue语句

continue语句用于跳过当前循环的剩余代码,直接开始下一次循环。

continue示例
#!/bin/bash

# 跳过偶数,只打印奇数
for num in 1 2 3 4 5 6 7 8 9 10
do
    if [ $((num % 2)) -eq 0 ]
    then
        continue
    fi
    echo "奇数: $num"
done
break和continue的层级控制

在嵌套循环中,可以指定break或continue要影响哪一层循环。

break N示例
#!/bin/bash

# 嵌套循环中的break
for i in 1 2 3
do
    echo "外层循环: $i"
    for j in 1 2 3
    do
        echo "内层循环: $j"
        if [ $j -eq 2 ]
        then
            break 2  # 退出两层循环
        fi
    done
done
continue N示例
#!/bin/bash

# 嵌套循环中的continue
for i in 1 2 3
do
    echo "外层循环: $i"
    for j in 1 2 3
    do
        if [ $j -eq 2 ]
        then
            continue 2  # 继续外层循环的下一次迭代
        fi
        echo "内层循环: $j"
    done
done
提示: break和continue后面可以跟一个数字N,表示要影响第N层循环。默认情况下,N=1,表示只影响当前循环。

实战示例

示例1: 系统监控脚本

使用流程控制创建一个系统监控脚本,检查系统资源使用情况。

#!/bin/bash

# 检查CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)

if [ $(echo "$cpu_usage > 80" | bc) -eq 1 ]
then
    echo "警告: CPU使用率过高 - $cpu_usage%"
else
    echo "CPU使用率正常 - $cpu_usage%"
fi

# 检查内存使用率
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')

if [ $(echo "$mem_usage > 90" | bc) -eq 1 ]
then
    echo "警告: 内存使用率过高 - $mem_usage%"
else
    echo "内存使用率正常 - $mem_usage%"
fi
示例2: 文件备份脚本

使用循环和条件判断创建一个文件备份脚本。

#!/bin/bash

# 备份目录
backup_dir="/backup"
source_dir="/home/user/documents"

# 创建备份目录
if [ ! -d "$backup_dir" ]
then
    mkdir -p "$backup_dir"
fi

# 备份文件
for file in "$source_dir"/*
do
    if [ -f "$file" ]
    then
        filename=$(basename "$file")
        backup_file="$backup_dir/${filename}.backup"

        if [ ! -f "$backup_file" ] || [ "$file" -nt "$backup_file" ]
        then
            cp "$file" "$backup_file"
            echo "已备份: $filename"
        else
            echo "跳过: $filename (已是最新)"
        fi
    fi
done
示例3: 用户管理脚本

使用case语句创建一个用户管理脚本。

#!/bin/bash

# 用户管理脚本
echo "用户管理菜单:"
echo "1. 添加用户"
echo "2. 删除用户"
echo "3. 修改用户"
echo "4. 列出用户"
echo "请输入选择(1-4):"
read choice

case $choice in
    1)
        echo "请输入用户名:"
        read username
        sudo useradd "$username"
        echo "用户 $username 已添加"
        ;;
    2)
        echo "请输入要删除的用户名:"
        read username
        sudo userdel "$username"
        echo "用户 $username 已删除"
        ;;
    3)
        echo "请输入要修改的用户名:"
        read username
        sudo usermod -c "修改的用户" "$username"
        echo "用户 $username 已修改"
        ;;
    4)
        echo "系统用户列表:"
        cut -d: -f1 /etc/passwd | sort
        ;;
    *)
        echo "无效选择"
        ;;
esac
示例4: 日志分析脚本

使用循环和条件判断分析日志文件。

#!/bin/bash

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

# 分析日志文件
while IFS= read -r line
do
    case $line in
        *error*|*ERROR*)
            echo "错误: $line"
            ((error_count++))
            ;;
        *warning*|*WARNING*)
            echo "警告: $line"
            ((warning_count++))
            ;;
    esac
done < "$log_file"

echo "分析完成:"
echo "错误数量: $error_count"
echo "警告数量: $warning_count"