Shell脚本的决策智慧:if、case及测试条件全解析

Shell脚本的决策智慧:if、case及测试条件全解析

一个强大的 Shell 脚本不仅能按顺序执行命令,更应该具备根据不同情况做出不同决策的能力。这就是条件判断语句的魅力所在。本文将带你深入理解 Shell 脚本中最核心的
if

case
语句,以及它们赖以工作的各种测试条件。


一、
if
语句:脚本的逻辑核心


if
语句是最基本也是最常用的条件判断结构。它的核心思想是:如果某个条件成立,那么就执行某些操作。

1. 基本语法:
if ... then ... fi

这是最简单的
if
结构。


#!/bin/bash

# 检查当前用户是否是 root
if [ "$(whoami)" == "root" ]; then
  echo "警告:你正在以 root 用户身份运行此脚本!"
fi

echo "脚本继续执行..."

解读
if
后面跟着一个测试条件
[ "$(whoami)" == "root" ]
。如果该条件为真(命令返回退出码 0),
then

fi
之间的代码块就会被执行。无论条件是否成立,脚本都会继续执行
fi
之后的代码。

2.
if-else
结构:两种选择

当你需要在条件成立和不成立时执行不同操作,可以使用
if-else


#!/bin/bash

LOG_FILE="/var/log/app.log"

if [ -w "$LOG_FILE" ]; then
  echo "日志文件可写,正在记录日志..."
  date >> "$LOG_FILE"
else
  echo "错误:日志文件 $LOG_FILE 不可写!"
  exit 1
fi

解读:如果文件可写(
-w
测试条件),则执行
then
块;否则,执行
else
块。

3.
if-elif-else
结构:多种选择

当你需要检查一系列互斥的条件时,
elif
(else if) 就派上用场了。


#!/bin/bash

read -p "请输入一个分数 (0-100): " score

if [ "$score" -ge 90 ]; then
  echo "优秀 (A)"
elif [ "$score" -ge 80 ]; then
  echo "良好 (B)"
elif [ "$score" -ge 60 ]; then
  echo "及格 (C)"
else
  echo "不及格 (F)"
fi

解读:脚本会从上到下依次检查条件,一旦某个
if

elif
的条件满足,就会执行其对应的代码块,然后直接跳到
fi
,不再检查后面的
elif

else


二、测试条件:
if
语句的大脑


if
语句本身只是一个框架,真正让它工作的是方括号
[

]

[[

]]
中的测试条件。

重要提示:在现代 Bash 脚本中,强烈推荐使用
[[ ... ]]
,因为它更健壮、功能更强,能避免很多
[ ... ]
的常见错误。

1. 文件与目录测试

操作符 描述 示例

-e "文件路径"
文件或目录是否存在
[[ -e "/etc/hosts" ]]

-f "文件路径"
是否为一个普通文件
[[ -f "script.sh" ]]

-d "文件路径"
是否为一个目录
[[ -d "/home/user" ]]

-r "文件路径"
是否可读
[[ -r "config.txt" ]]

-w "文件路径"
是否可写
[[ -w "log.txt" ]]

-x "文件路径"
是否可执行
[[ -x "run.sh" ]]

-s "文件路径"
文件是否存在且非空
[[ -s "data.csv" ]]

2. 字符串测试

操作符 描述 示例(推荐使用
[[...]]

==

=
字符串内容是否相等
[[ "$str1" == "$str2" ]]

!=
字符串内容是否不相等
[[ "$USER" != "guest" ]]

-z "字符串"
字符串是否为空(长度为零)
[[ -z "$password" ]]

-n "字符串"
字符串是否不为空
[[ -n "$username" ]]

3. 整数比较测试

操作符 描述 示例

-eq
等于 (equal)
[[ $count -eq 10 ]]

-ne
不等于 (not equal)
[[ $lines -ne 0 ]]

-gt
大于 (greater than)
[[ $age -gt 18 ]]

-ge
大于或等于 (greater or equal)
[[ $score -ge 60 ]]

-lt
小于 (less than)
[[ $retries -lt 5 ]]

-le
小于或等于 (less or equal)
[[ $temperature -le 0 ]]

4. 逻辑组合


&&
:逻辑与 (AND) –
[[ $age -ge 18 && "$country" == "CN" ]]

||
:逻辑或 (OR) –
[[ "$role" == "admin" || "$role" == "root" ]]

!
:逻辑非 (NOT) –
[[ ! -f "lock.file" ]]
(如果 lock.file 不存在)

5. if 高级使用


if
语句的威力取决于你如何运用测试条件。我们推荐使用
[[ ... ]]
,因为它更健壮、功能更强。

核心技巧:使用
!
进行逻辑非


!
操作符可以反转任何测试条件的结果。这在“如果不存在则…”的场景中非常有用。


[[ ! -f "file.txt" ]]
: 如果 “file.txt” 不是一个文件(或不存在),则条件为真。
[[ ! -d "/tmp/data" ]]
: 如果 “/tmp/data” 不是一个目录,则条件为真。
[[ ! "$user" == "root" ]]
: 如果
$user
的值不等于 “root”,则条件为真。


CONFIG_FILE="app.conf"

if [[ ! -r "$CONFIG_FILE" ]]; then
  echo "错误:配置文件 $CONFIG_FILE 不存在或不可读!"
  exit 1
fi

6. 高级技巧:用
&&

||
实现简洁的控制流

在 Shell 中,命令的成功与否由其退出码决定(0 代表成功,非 0 代表失败)。我们可以利用这一点,通过逻辑与
&&
和逻辑或
||
来构建极其简洁的命令链,从而替代简单的
if-then-else
结构。


command1 && command2
: “与”链。只有当
command1
成功时(退出码为 0),才会执行
command2

command1 || command2
: “或”链。只有当
command1
失败时(退出码非 0),才会执行
command2

实战解析:一行代码的智慧

让我们来解构你提到的那段惊艳的脚本:


[ ! -f density.rst ] && { ${MDRUN_AMBER} ... > log 2>&1; } || { { mpirun ... >> log2 2>&1; } || { echo "Failed..." && exit; } }

这段代码看似复杂,但遵循着清晰的逻辑:尝试A,如果A失败,则尝试B,如果B也失败,则报错。


[ ! -f density.rst ] && ...

判断:如果文件
density.rst
存在…动作
&&
意味着,只有在上述判断为真(文件确实不存在)时,才会执行后面的整个大括号
{...}
命令组。


{ ${MDRUN_AMBER} ...; } || ...

首次尝试:执行
{...}
中的
MDRUN_AMBER
命令。
{ ...; }
将多个命令组合成一个逻辑单元。
> MD_mdrun.log 2>&1
表示将标准输出和标准错误都重定向到日志文件。失败后备
||
意味着,如果
MDRUN_AMBER
命令失败了(返回了非零退出码),则执行
||
后面的命令组。


{ { mpirun ...; } || { echo "Failed..." && exit; } }

二次尝试:执行
mpirun
命令。最终失败处理
||
意味着,如果
mpirun
命令也失败了,就执行最后的
{...}
部分:打印错误信息
echo "Failed..."
并且 (
&&
) 立即退出脚本
exit

总结:这行代码优雅地实现了一个健壮的执行链:检查前提 -> 尝试主方案 -> 尝试备用方案 -> 最终错误处理。这种写法在需要确保关键步骤必须成功时非常有用。


三、
case
语句:
if
的优雅替代品

当你的判断条件都围绕同一个变量的多种可能值时,使用
case
语句会比冗长的
if-elif
链条更加清晰易读。

1.
case
语法


case
语句将一个变量的内容与多个模式(pattern)进行匹配。


#!/bin/bash

# 一个简单的服务控制脚本
ACTION="$1" # 第一个命令行参数

case "$ACTION" in
  start)
    echo "正在启动服务..."
    # 启动服务的命令
    ;;
  stop)
    echo "正在停止服务..."
    # 停止服务的命令
    ;;
  restart)
    echo "正在重启服务..."
    # 重启服务的命令
    ;;
  status)
    echo "正在检查服务状态..."
    # 检查状态的命令
    ;;
  *) # 匹配所有其他情况 (默认选项)
    echo "用法: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac

exit 0

解读


case "$ACTION" in
:开始
case
语句,判断
$ACTION
变量的值。
start)
:一个模式。如果
$ACTION
的值是 “start”,则执行其下的代码。
;;
:每个代码块的结束符,类似于 C 语言中的
break

*)
:一个通配符模式,用于捕获所有未被前面模式匹配到的值,通常作为默认或错误处理分支。
esac

case
语句的结束标记(
case
的倒写)。

2. 高级应用:
while
+
case
+
shift
构建专业参数解析器

要处理
-p 123

--user admin
这样的命令行参数,我们需要一个循环来遍历所有参数。
while

case

shift
的组合是实现此功能的黄金标准。

代码范例:


#!/bin/bash

# 默认值
concentration=""
min_distance=""
PDBID=""
boundary=""
forcefield=""

# 帮助函数
show_help() {
  echo "用法: $0 [选项]..."
  echo "  -c, --concentration  设置浓度"
  echo "  -d, --distance       设置最小距离"
  # ... 其他帮助信息
}

# --- 核心解析循环 ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    -c|--concentration)
      concentration="$2" # 将选项的值($2)赋给变量
      shift 2            # 消耗掉选项(-c)和它的值,共2个参数
      ;;
    -d|--distance)
      min_distance="$2"
      shift 2
      ;;
    -p|--pdbid)
      PDBID="$2"
      shift 2
      ;;
    -h|--help)
      show_help # 调用帮助函数
      exit 0    # 正常退出
      ;;
    *)
      echo "未知参数: $1"
      exit 1
      ;;
  esac
done

echo "--- 解析结果 ---"
echo "浓度: ${concentration:-未设置}"
echo "距离: ${min_distance:-未设置}"
echo "PDB ID: ${PDBID:-未设置}"

# 脚本后续逻辑...

智慧解析:


while [[ $# -gt 0 ]]
: 只要还存在命令行参数(
$#
代表参数总数),循环就继续。
case "$1" in
:
case
语句负责判断当前第一个参数 (
$1
) 是哪个选项。
-c|--concentration)
: 这是模式匹配的精髓。
|
代表“或”,因此这个模式可以同时匹配短选项
-c
和长选项
--concentration

concentration="$2"
: 如果匹配成功,脚本认为紧跟在选项后面的第二个参数 (
$2
) 就是这个选项的值,并将其赋给相应的变量。
shift 2
: 这是最关键的一步。
shift
命令会丢弃掉位置参数。
shift 2
会丢弃掉
$1

$2
,然后原来的
$3
变成新的
$1
,原来的
$4
变成新的
$2
,以此类推。这样,下一次循环开始时,
$1
就已经是下一个待处理的选项了。对于像
-h
这样不带值的选项,我们只需
shift
(或
shift 1
) 一次。


三、结论:如何选择?

使用
if
语句

进行复杂的逻辑判断(数值范围、多变量、文件属性)。当你需要利用
&&

||
构建简洁的、依赖于命令成功/失败的执行链时。

使用
case
语句

当你的判断逻辑基于单个变量固定模式匹配时,它比
if-elif
更清晰。与
while

shift
结合,成为解析命令行参数的不二之选

通过掌握这些从基础到高级的决策工具,你将能够编写出逻辑严密、健壮可靠且用户体验良好的专业级 Shell 脚本。

© 版权声明

相关文章

暂无评论

none
暂无评论...