欢迎光临
我们一直在努力

十、Linux Shell脚本:流程控制语句

作者:IvanCodes 日期:2025年8月10日 专栏:Linux教程

在掌握了Shell脚本的变量与运算之后,流程控制构建复杂和实用脚本关键。它允许脚本根据不同的条件选择执行路径,或重复执行特定任务,从而实现脚本灵活性与自动化

思维导图

在这里插入图片描述 在这里插入图片描述

在这里插入图片描述

一、条件判断

if 语句是最基本条件控制结构,它评估一个命令退出状态码 (exit code)。如果退出码为 0 (成功),则条件为真;如果为非 0 (失败),则条件为假

if 的基本结构

格式:

if [ 条件判断 ]; then
# 条件为真时执行的代码块
fi

代码示例:检查文件是否存在

#!/bin/bash
TARGET_FILE="/etc/hosts"

if [ -f "$TARGET_FILE" ]; then
echo "文件 '$TARGET_FILE' 存在。"
fi

if…else 结构

格式:

if [ 条件判断 ]; then
# 条件为真时执行的代码块
else
# 条件为假时执行的代码块
fi

代码示例:判断目录是否存在

#!/bin/bash
TARGET_DIR="/var/log/non_existent_dir"

if [ -d "$TARGET_DIR" ]; then
echo "目录 '$TARGET_DIR' 存在。"
else
echo "目录 '$TARGET_DIR' 不存在,将尝试创建。"
mkdir -p "$TARGET_DIR"
fi

if…elif…else 结构

格式:

if [ 条件1 ]; then
# 条件1为真时执行
elif [ 条件2 ]; then
# 条件1为假,但条件2为真时执行
else
# 以上所有条件都为假时执行
fi

代码示例:根据HTTP状态码判断响应

#!/bin/bash
HTTP_CODE=200

if [ $HTTP_CODE -eq 200 ]; then
echo "请求成功 (OK)"
elif [ $HTTP_CODE -eq 404 ]; then
echo "资源未找到 (Not Found)"
elif [ $HTTP_CODE -eq 500 ]; then
echo "服务器内部错误 (Internal Server Error)"
else
echo "收到未知的HTTP状态码: $HTTP_CODE"
fi

条件判断的实现:test 和 [ ]

在Shell中,if 后的条件通常由 test 命令或其等价形式 [ … ] 来实现。[[ … ]] 是 [ … ] 的扩展版本,提供了更多功能 (如模式匹配、逻辑与/或)。

常见判断类型:

文件测试: -f (是普通文件?), -d (是目录?), -e (存在?), -s (大小非0?), -r (可读?), -w (可写?), -x (可执行?) 字符串比较: "$str1" = "$str2", "$str1" != "$str2", -z "$str" (字符串为空?), -n "$str" (字符串非空?) 整数比较: -eq (等于), -ne (不等于), -gt (大于), -ge (大于等于), -lt (小于), -le (小于等于)

二、循环结构

循环用于重复执行一段代码,直到满足某个退出条件。

for 循环

for 循环擅长遍历一个列表 (字符串、文件名、数字序列等) 或进行C语言风格数值循环

格式 (遍历列表):

for variable_name in item1 item2 item3 ...; do
# 循环体
done

代码示例 (遍历并重命名文件):

#!/bin/bash
# 将所有 .txt 文件重命名为 .txt.bak
for filename in *.txt; do
if [ -f "$filename" ]; then
echo "正在备份: $filename -> ${filename}.bak"
mv "$filename" "${filename}.bak"
fi
done

格式 (C风格数值循环):

for (( initialization; condition; step )); do
# 循环体
done

代码示例 (执行三次ping测试):

#!/bin/bash
TARGET_HOST="8.8.8.8"

for (( i=1; i<=3; i++ )); do
echo "— 第 $i 次 PING 测试 —"
ping -c 1 "$TARGET_HOST"
done

while 循环

while 循环在每次迭代前检查条件,只要条件为真,就继续执行循环体。

格式:

while [ 条件判断 ]; do
# 循环体
done

代码示例:逐行读取文件

#!/bin/bash
CONFIG_FILE="/etc/fstab"

while read -r line; do
# 忽略注释和空行
if [[ "$line" =~ ^# || -z "$line" ]]; then
continue
fi
echo "读取到配置行: $line"
done < "$CONFIG_FILE"

until 循环

until 循环与 while 逻辑相反:只要条件为假,就继续执行循环体,直到条件变为真才停止。

格式:

until [ 条件判断 ]; do
# 循环体
done

代码示例:等待服务端口启动

#!/bin/bash
PORT=8080
TIMEOUT=10
COUNT=0

until nc -z localhost $PORT >/dev/null 2>&1; do
if [ $COUNT -ge $TIMEOUT ]; then
echo "等待端口 $PORT 超时!"
exit 1
fi
echo "端口 $PORT 尚未启动,等待1秒…"
sleep 1
COUNT=$((COUNT + 1))
done

echo "端口 $PORT 已成功启动!"

循环控制:break 和 continue

  • break: 立即当前循环中完全跳出
  • continue: 跳过当前循环的剩余部分,直接开始下一次迭代。

代码示例:在循环中处理文件

#!/bin/bash
for file in /var/log/*; do
if [ -d "$file" ]; then
continue # 如果是目录,则跳过
fi

echo "正在处理文件: $file"

if [ -s "$file" ] && grep -q "ERROR" "$file"; then
echo "在文件 '$file' 中找到错误,停止处理。"
break # 找到错误后,完全停止
fi
done

三、分支选择

case 语句提供了一种更清晰的方式来处理多重条件分支,是 if…elif…else 的一种替代方案,特别适合基于单个变量值进行匹配

格式:

case $variable in
pattern1)
# 匹配 pattern1 时执行
;;
pattern2|pattern3)
# 匹配 pattern2 或 pattern3 时执行
;;
*)
# 默认情况,当以上模式都不匹配时执行
;;
esac

代码示例:脚本参数解析

#!/bin/bash
ACTION=$1

case $ACTION in
start)
echo "正在启动服务…"
# systemctl start my_service
;;
stop)
echo "正在停止服务…"
# systemctl stop my_service
;;
status)
echo "检查服务状态…"
# systemctl status my_service
;;
*)
echo "用法: $0 {start|stop|status}"
exit 1
;;
esac


练习题

题目:

  • 文件权限检查:写一个脚本,接收一个文件名作为参数 ($1)。脚本需要判断当前用户对该文件是否同时拥有读、写、执行权限。如果同时拥有,打印 “Full permissions granted”;否则打印 “Permissions incomplete”。
  • 字符串与逻辑判断:写一个脚本,检查变量 ENVIRONMENT 的值。如果值是 production 并且 变量 FORCE_DEPLOY 的值不是 true,则打印 “Safety check passed: Not a forced production deploy.” 并退出;否则,打印 “Proceeding with deployment.”。
  • C风格 for 循环与算术:使用C风格的 for 循环,打印出从10到20之间所有的偶数 (包括10和20)。
  • for 循环与通配符:写一个脚本,查找 /var/log 目录下所有以 .log 结尾的非空文件,并打印出它们的文件名。
  • while 循环读取标准输入:写一个脚本,持续读取用户从键盘输入的内容,直到用户输入 quit 为止。对于非 quit 的输入,脚本应该将其回显到屏幕上。
  • until 循环与命令退出码:grep 命令在找到匹配项时退出码为0,找不到时为1。写一个 until 循环,每隔2秒检查一次系统日志 (/var/log/messages 或 journalctl -f 的输出,为简化可检查一个普通文件) 是否出现了 “critical error” 字符串,一旦出现就打印 “Critical error detected!” 并退出。
  • 嵌套循环与 break n:写一个嵌套循环。外层循环从1到3,内层循环从1到3。在内层循环中,如果内外两个循环变量 (i 和 j) 相等,则同时跳出内外两层循环。每次循环都打印当前的 i 和 j 的值。
  • case 语句与通配符:写一个 case 语句,判断一个文件名变量 FILENAME 的文件类型。如果文件名以 .log 结尾,打印 “Log file”;如果以 .tar.gz 或 .tgz 结尾,打印 “Compressed archive”;如果以 .sh 结尾,打印 “Shell script”;其他情况打印 “Unknown file type”。
  • select 菜单 (高级):select 是一个特殊的循环结构,用于创建交互式菜单。写一个脚本,使用 select 让用户从 “Start”, “Stop”, “Restart”, “Exit” 四个选项中选择一个操作,并根据用户的选择打印相应的信息。当用户选择 “Exit” 时,脚本退出。
  • 答案与解析:

  • 文件权限检查:
  • #!/bin/bash
    if [ -z "$1" ]; then
    echo "用法: $0 <文件名>"
    exit 1
    fi

    if [ -r "$1" ] && [ -w "$1" ] && [ -x "$1" ]; then
    echo "Full permissions granted"
    else
    echo "Permissions incomplete"
    fi

    • 解析: if 语句中的 -r, -w, -x 是文件测试操作符,分别检查读、写、执行权限。&& 是逻辑与操作符,要求所有条件都为真才执行 then 块。
  • 字符串与逻辑判断:
  • #!/bin/bash
    ENVIRONMENT="production"
    FORCE_DEPLOY="false"

    if [[ "$ENVIRONMENT" == "production" && "$FORCE_DEPLOY" != "true" ]]; then
    echo "Safety check passed: Not a forced production deploy."
    exit 0
    else
    echo "Proceeding with deployment."
    fi

    • 解析: 使用了 [[ … ]] 扩展测试,它内部支持 && (逻辑与) 和 != (字符串不等于) 操作符,语法更自然
  • C风格 for 循环与算术:
  • #!/bin/bash
    for (( num=10; num<=20; num+=2 )); do
    echo $num
    done

    • 解析: C风格的 for 循环通过初始化 num=10条件 num<=20,以及步进 num+=2 来精确控制循环,直接打印出范围内的偶数。
  • for 循环与通配符:
  • #!/bin/bash
    for logfile in /var/log/*.log; do
    if [ -s "$logfile" ]; then
    echo "找到非空日志文件: $(basename "$logfile")"
    fi
    done

    • 解析: *.log 是一个通配符,for 循环会遍历所有匹配的文件名。-s 文件测试操作符用于判断文件大小是否大于零。basename 命令用于提取文件名,去除路径。
  • while 循环读取标准输入:
  • #!/bin/bash
    echo "请输入内容 (输入 'quit' 退出):"
    while read -r input_line; do
    if [ "$input_line" == "quit" ]; then
    break
    fi
    echo "你输入了: $input_line"
    done

    • 解析: while read -r input_line 是读取标准输入标准模式。循环会一直持续,直到 read 命令失败 (例如,用户按下Ctrl+D) 或遇到 break。
  • until 循环与命令退出码:
  • #!/bin/bash
    LOG_FILE_TO_CHECK="my_app.log"
    touch $LOG_FILE_TO_CHECK # 创建一个空文件用于测试

    echo "正在监控 '$LOG_FILE_TO_CHECK' …"
    # 在另一个终端执行 echo "critical error" >> my_app.log 来触发
    until grep -q "critical error" "$LOG_FILE_TO_CHECK"; do
    sleep 2
    done

    echo "Critical error detected!"

    • 解析: until 循环的条件是命令本身 (grep -q …)。只要 grep 找不到字符串 (退出码非0,条件为假),循环就继续。一旦找到 (退出码为0,条件为真),循环终止
  • 嵌套循环与 break n:
  • #!/bin/bash
    for (( i=1; i<=3; i++ )); do
    echo "外层循环: i=$i"
    for (( j=1; j<=3; j++ )); do
    echo " 内层循环: j=$j"
    if [ $i -eq $j ]; then
    echo " i 等于 j,跳出所有循环!"
    break 2 # '2' 表示跳出两层循环
    fi
    done
    done

    • 解析: break n 命令可以跳出指定层数的循环。break 1 (或 break) 只跳出当前层,break 2 跳出当前层和其外一层。
  • case 语句与通配符:
  • #!/bin/bash
    FILENAME="archive-2023.tar.gz"

    case $FILENAME in
    *.log)
    echo "Log file"
    ;;
    *.tar.gz|*.tgz)
    echo "Compressed archive"
    ;;
    *.sh)
    echo "Shell script"
    ;;
    *)
    echo "Unknown file type"
    ;;
    esac

    • 解析: case 语句的模式支持通配符,如 * (匹配任意字符序列)。| 用于分隔多个模式,表示“或”。
  • select 菜单:
  • #!/bin/bash
    PS3="请选择一个操作 (输入数字): "
    options=("Start" "Stop" "Restart" "Exit")

    select opt in "${options[@]}"; do
    case $opt in
    "Start")
    echo "正在启动…"
    ;;
    "Stop")
    echo "正在停止…"
    ;;
    "Restart")
    echo "正在重启…"
    ;;
    "Exit")
    echo "退出脚本。"
    break
    ;;
    *)
    echo "无效选项 '$REPLY',请重新选择。"
    ;;
    esac
    done

    • 解析: select 会自动生成一个带编号的菜单。用户的输入编号被翻译对应的选项值 (赋给变量opt),而原始输入则保存在 $REPLY 中。
    赞(0)
    未经允许不得转载:171主机测评 » 十、Linux Shell脚本:流程控制语句
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址