linux shell

关注公众号 jb51net

关闭
首页 > 脚本专栏 > linux shell > Linux Shell脚本条件判断语句用法

Linux Shell脚本中的条件判断语句的使用方法

作者:Jinkxs

在 Linux 系统中,Shell 脚本是自动化任务、系统管理、部署流程等场景下不可或缺的工具,而条件判断语句,则是 Shell 脚本实现逻辑控制的核心语法之一,本文将从基础语法讲起,逐步深入到实际应用场景帮助你更好地理解 Shell 条件判断的本质,需要的朋友可以参考下

在 Linux 系统中,Shell 脚本是自动化任务、系统管理、部署流程等场景下不可或缺的工具。而条件判断语句,则是 Shell 脚本实现逻辑控制的核心语法之一。无论是简单的文件存在性检查,还是复杂的多分支业务逻辑,都离不开 ifcasetest[[ ]][ ] 这些关键字与结构。

本文将从基础语法讲起,逐步深入到实际应用场景,并穿插 Java 代码作为对比示例,帮助你更好地理解 Shell 条件判断的本质。同时,我们也会借助 mermaid 图表 可视化展示逻辑流程,增强理解效果。

一、什么是 Shell 条件判断?

在 Shell 中,条件判断用于根据某个表达式或命令的“真假”来决定是否执行某段代码。其核心思想与几乎所有编程语言一致 —— “如果满足 A,则做 B;否则,做 C”。

Shell 的条件判断主要依赖以下几种结构:

Shell 不像高级语言那样有布尔类型(true/false),它使用的是退出状态码(Exit Status):0 表示“真”,非 0 表示“假”。

#!/bin/bash

if [ 1 -eq 1 ]; then
    echo "1 等于 1,条件成立 ✅"
fi

小贴士:在终端中,你可以随时用 echo $? 查看上一条命令的退出状态码。

二、基础 if 语句结构

2.1 单分支 if

最简单的形式是只判断一个条件,满足则执行:

if [ condition ]; then
    # 执行语句
fi

示例:

#!/bin/bash

read -p "请输入你的年龄: " age

if [ $age -ge 18 ]; then
    echo "你已成年,可以投票 🗳️"
fi

2.2 双分支 if-else

当需要处理“否则”的情况时,使用 else

if [ condition ]; then
    # 条件为真时执行
else
    # 条件为假时执行
fi

示例:

#!/bin/bash

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

if [ $score -ge 60 ]; then
    echo "🎉 恭喜你,及格了!"
else
    echo "😔 很遗憾,你需要补考。"
fi

2.3 多分支 if-elif-else

多个条件判断使用 elif(即 else if):

if [ condition1 ]; then
    # 执行语句1
elif [ condition2 ]; then
    # 执行语句2
else
    # 其他情况
fi

示例:

#!/bin/bash

read -p "请输入成绩等级 (A/B/C/D): " grade

if [ "$grade" = "A" ]; then
    echo "优秀 🌟"
elif [ "$grade" = "B" ]; then
    echo "良好 👍"
elif [ "$grade" = "C" ]; then
    echo "及格 😅"
elif [ "$grade" = "D" ]; then
    echo "不及格 ❌"
else
    echo "输入无效,请输入 A/B/C/D"
fi

三、Java 对比示例

为了帮助熟悉 Java 的开发者快速理解 Shell 的条件判断,下面给出上述脚本对应的 Java 版本:

3.1 单分支 Java 示例

import java.util.Scanner;

public class AgeCheck {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入你的年龄: ");
        int age = scanner.nextInt();

        if (age >= 18) {
            System.out.println("你已成年,可以投票 🗳️");
        }
    }
}

3.2 双分支 Java 示例

import java.util.Scanner;

public class ScoreCheck {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入分数 (0-100): ");
        int score = scanner.nextInt();

        if (score >= 60) {
            System.out.println("🎉 恭喜你,及格了!");
        } else {
            System.out.println("😔 很遗憾,你需要补考。");
        }
    }
}

3.3 多分支 Java 示例

import java.util.Scanner;

public class GradeCheck {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入成绩等级 (A/B/C/D): ");
        String grade = scanner.next();

        if (grade.equals("A")) {
            System.out.println("优秀 🌟");
        } else if (grade.equals("B")) {
            System.out.println("良好 👍");
        } else if (grade.equals("C")) {
            System.out.println("及格 😅");
        } else if (grade.equals("D")) {
            System.out.println("不及格 ❌");
        } else {
            System.out.println("输入无效,请输入 A/B/C/D");
        }
    }
}

对比发现:Shell 的语法更简洁,但缺乏强类型和 IDE 支持,调试起来不如 Java 方便。但在系统级脚本中,Shell 更轻量高效。

四、条件测试命令详解

Shell 中常用的测试方式有三种:

  1. [ expression ] —— POSIX 标准,兼容性好
  2. [[ expression ]] —— Bash 扩展,功能更强
  3. test expression —— 与 [ ] 等价,常用于可读性要求高的脚本

4.1 文件测试操作符

操作符含义
-e file文件存在
-f file是普通文件
-d file是目录
-r file可读
-w file可写
-x file可执行
-s file文件非空

示例:

#!/bin/bash

FILE="/etc/passwd"

if [ -e "$FILE" ]; then
    echo "✅ 文件 $FILE 存在"
else
    echo "❌ 文件 $FILE 不存在"
fi

if [ -f "$FILE" ]; then
    echo "📄 它是一个普通文件"
fi

if [ -r "$FILE" ]; then
    echo "📖 你有读取权限"
fi

4.2 字符串比较操作符

操作符含义
str1 = str2相等(注意是单个 =
str1 != str2不相等
-z str字符串长度为 0(空)
-n str字符串长度不为 0(非空)

注意:在 [ ] 中,建议对变量加双引号,避免空值导致语法错误。

#!/bin/bash

read -p "请输入用户名: " username

if [ -z "$username" ]; then
    echo "⛔ 用户名不能为空"
elif [ "$username" = "admin" ]; then
    echo "👑 欢迎管理员登录"
else
    echo "👋 欢迎用户 $username"
fi

4.3 数值比较操作符

操作符含义
-eq等于
-ne不等于
-gt大于
-lt小于
-ge大于等于
-le小于等于
#!/bin/bash

read -p "请输入一个数字: " num

if [ $num -gt 100 ]; then
    echo "📈 数字大于 100"
elif [ $num -eq 100 ]; then
    echo "🎯 正好是 100"
else
    echo "📉 小于 100"
fi

五、[[ ]] 与 [ ] 的区别

虽然 [ ] 是 POSIX 标准,广泛兼容,但 [[ ]] 是 Bash 的扩展语法,支持更多特性:

5.1 使用[[ ]]进行正则匹配

#!/bin/bash

read -p "请输入邮箱: " email

if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "📧 邮箱格式正确"
else
    echo "❌ 邮箱格式错误"
fi

5.2 使用[[ ]]的逻辑运算符

#!/bin/bash

read -p "请输入年龄: " age
read -p "请输入城市: " city

if [[ $age -ge 18 && "$city" == "北京" ]]; then
    echo "🎉 北京地区的成年人,可申请居住证"
elif [[ $age -lt 18 || "$city" != "北京" ]]; then
    echo "📌 不符合条件"
fi

推荐:在 Bash 脚本中优先使用 [[ ]],除非你需要兼容老式 Shell(如 dash、sh)。

六、case 语句:多分支选择利器

当面对多个固定选项时,case 语句比一连串 if-elif 更清晰、高效。

6.1 基本语法

case 变量 in
    模式1)
        命令1
        ;;
    模式2)
        命令2
        ;;
    *)
        默认命令
        ;;
esac

6.2 实际示例:菜单选择系统

#!/bin/bash

echo "请选择操作:"
echo "1) 查看系统信息"
echo "2) 查看磁盘使用"
echo "3) 查看内存使用"
echo "4) 退出"

read -p "请输入选项 [1-4]: " choice

case $choice in
    1)
        echo "💻 系统信息:"
        uname -a
        ;;
    2)
        echo "💽 磁盘使用情况:"
        df -h
        ;;
    3)
        echo "🧠 内存使用情况:"
        free -h
        ;;
    4)
        echo "👋 再见!"
        exit 0
        ;;
    *)
        echo "❌ 无效选项,请输入 1-4"
        ;;
esac

6.3 支持通配符和多个模式

#!/bin/bash

read -p "请输入文件扩展名 (如 .txt, .jpg): " ext

case $ext in
    .txt|.md|.log)
        echo "📄 这是一个文本文件"
        ;;
    .jpg|.png|.gif)
        echo "🖼️ 这是一个图片文件"
        ;;
    .mp4|.avi|.mkv)
        echo "🎬 这是一个视频文件"
        ;;
    *)
        echo "❓ 未知文件类型"
        ;;
esac

七、逻辑运算符与组合条件

Shell 支持使用 &&(与)、||(或)、!(非)组合多个条件。

7.1 使用&&和||在命令中

# 成功执行 command1 后才执行 command2
command1 && command2

# command1 失败才执行 command2
command1 || command2

示例:

#!/bin/bash

ping -c 1 google.com >/dev/null 2>&1 && echo "🌐 网络通畅" || echo "🚫 网络不通"

7.2 在if中组合条件

#!/bin/bash

read -p "请输入用户名: " user
read -p "请输入密码: " pass

if [ -n "$user" ] && [ -n "$pass" ]; then
    echo "✅ 用户名和密码都不为空"
else
    echo "❌ 用户名或密码为空"
fi

或者使用 [[ ]] 更简洁:

if [[ -n $user && -n $pass ]]; then
    echo "✅ 用户名和密码都不为空"
fi

八、常见陷阱与最佳实践

8.1 变量未加引号导致错误

# ❌ 错误示范:当 name 为空或含空格时会报错
if [ $name = "Alice" ]; then ...

# ✅ 正确做法:始终加双引号
if [ "$name" = "Alice" ]; then ...

8.2 数值比较误用字符串操作符

# ❌ 错误:使用 = 比较数值,结果可能不符合预期
if [ $a = $b ]; then ...

# ✅ 正确:使用 -eq
if [ $a -eq $b ]; then ...

8.3 忘记空格

# ❌ 错误:[ 和 ] 两边必须有空格
if [$a -eq $b]; then ...

# ✅ 正确
if [ $a -eq $b ]; then ...

8.4 使用(( ))进行算术判断(仅限整数)

#!/bin/bash

a=5
b=3

if (( a > b )); then
    echo "$a 大于 $b"
fi

# 也可以直接计算
if (( a + b > 7 )); then
    echo "两数之和大于 7"
fi

提示:(( )) 是 Bash 的算术扩展,适合纯数学运算,不支持字符串。

九、结合函数与条件判断

将条件判断封装进函数,提高代码复用性和可读性。

#!/bin/bash

# 判断是否为偶数
is_even() {
    local num=$1
    if (( num % 2 == 0 )); then
        return 0  # true
    else
        return 1  # false
    fi
}

read -p "请输入一个数字: " n

if is_even $n; then
    echo "🔢 $n 是偶数"
else
    echo "🔢 $n 是奇数"
fi

十、实战案例:自动化部署脚本

下面是一个模拟的部署脚本,包含多个条件判断:

#!/bin/bash

APP_NAME="myapp"
DEPLOY_DIR="/opt/$APP_NAME"
BACKUP_DIR="/backup/$APP_NAME"
LOG_FILE="/var/log/deploy.log"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

# 检查日志目录是否存在
if [ ! -d "/var/log" ]; then
    log "❌ /var/log 不存在,无法记录日志"
    exit 1
fi

# 检查部署目录
if [ ! -d "$DEPLOY_DIR" ]; then
    log "🏗️  创建部署目录 $DEPLOY_DIR"
    mkdir -p "$DEPLOY_DIR"
fi

# 检查备份目录
if [ ! -d "$BACKUP_DIR" ]; then
    log "🗃️  创建备份目录 $BACKUP_DIR"
    mkdir -p "$BACKUP_DIR"
fi

# 备份旧版本
if [ -f "$DEPLOY_DIR/app.jar" ]; then
    BACKUP_FILE="$BACKUP_DIR/app_$(date +%Y%m%d_%H%M%S).jar"
    log "📦 备份旧版本到 $BACKUP_FILE"
    cp "$DEPLOY_DIR/app.jar" "$BACKUP_FILE"
fi

# 检查新包是否存在
NEW_JAR="./target/app.jar"
if [ ! -f "$NEW_JAR" ]; then
    log "❌ 新版本包 $NEW_JAR 不存在,部署终止"
    exit 1
fi

# 复制新包
log "🚚 部署新版本..."
cp "$NEW_JAR" "$DEPLOY_DIR/app.jar"

# 设置权限
chmod +x "$DEPLOY_DIR/app.jar"

# 检查 Java 是否安装
if ! command -v java &> /dev/null; then
    log "❌ Java 未安装,无法启动应用"
    exit 1
fi

# 启动应用
cd "$DEPLOY_DIR"
nohup java -jar app.jar > app.log 2>&1 &

PID=$!
log "✅ 应用启动成功,PID: $PID"

sleep 3

# 检查进程是否存活
if ps -p $PID > /dev/null; then
    log "🟢 应用运行中"
else
    log "🔴 应用启动失败,请检查日志"
    exit 1
fi

log "🎉 部署完成!"

十一、Java 中的类似部署逻辑对比 🆚

下面是用 Java 实现类似部署逻辑的简化版:

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DeployManager {
    private static final String APP_NAME = "myapp";
    private static final String DEPLOY_DIR = "/opt/" + APP_NAME;
    private static final String BACKUP_DIR = "/backup/" + APP_NAME;
    private static final String LOG_FILE = "/var/log/deploy.log";
    public static void main(String[] args) {
        try {
            log("开始部署...");
            if (!isDirectoryExists("/var/log")) {
                log("❌ /var/log 不存在,无法记录日志");
                System.exit(1);
            }
            createDirectoryIfNotExists(DEPLOY_DIR);
            createDirectoryIfNotExists(BACKUP_DIR);
            backupOldVersion(DEPLOY_DIR + "/app.jar", BACKUP_DIR);
            String newJar = "./target/app.jar";
            if (!isFileExists(newJar)) {
                log("❌ 新版本包不存在,部署终止");
                System.exit(1);
            }
            copyFile(newJar, DEPLOY_DIR + "/app.jar");
            log("✅ 新版本部署完成");
            if (!isCommandAvailable("java")) {
                log("❌ Java 未安装");
                System.exit(1);
            }
            Process process = startApplication(DEPLOY_DIR);
            Thread.sleep(3000);
            if (isProcessAlive(process)) {
                log("🟢 应用运行中,PID: " + process.pid());
            } else {
                log("🔴 应用启动失败");
                System.exit(1);
            }
            log("🎉 部署完成!");
        } catch (Exception e) {
            log("💥 部署异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
    private static void log(String message) {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        String logMessage = timestamp + " - " + message;
        System.out.println(logMessage);
        appendToFile(LOG_FILE, logMessage);
    }
    private static boolean isDirectoryExists(String path) {
        File dir = new File(path);
        return dir.exists() && dir.isDirectory();
    }
    private static void createDirectoryIfNotExists(String path) throws IOException {
        File dir = new File(path);
        if (!dir.exists()) {
            if (dir.mkdirs()) {
                log("🏗️  创建目录: " + path);
            }
        }
    }
    private static void backupOldVersion(String filePath, String backupDir) throws IOException {
        File file = new File(filePath);
        if (file.exists()) {
            String backupName = "app_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".jar";
            String backupPath = backupDir + "/" + backupName;
            copyFile(filePath, backupPath);
            log("📦 备份旧版本到: " + backupPath);
        }
    }
    private static void copyFile(String src, String dest) throws IOException {
        File source = new File(src);
        File destination = new File(dest);
        // 简化实现,实际应使用 Files.copy()
        if (source.exists()) {
            // 模拟复制
            destination.createNewFile();
        }
    }
    private static boolean isFileExists(String path) {
        return new File(path).exists();
    }
    private static boolean isCommandAvailable(String command) {
        try {
            Process p = Runtime.getRuntime().exec(new String[]{"which", command});
            return p.waitFor() == 0;
        } catch (Exception e) {
            return false;
        }
    }
    private static Process startApplication(String dir) throws IOException {
        ProcessBuilder pb = new ProcessBuilder("java", "-jar", "app.jar");
        pb.directory(new File(dir));
        pb.redirectOutput(new File(dir + "/app.log"));
        pb.redirectErrorStream(true);
        return pb.start();
    }
    private static boolean isProcessAlive(Process process) {
        try {
            process.exitValue();
            return false; // 已退出
        } catch (IllegalThreadStateException e) {
            return true; // 仍在运行
        }
    }
    private static void appendToFile(String filePath, String content) {
        try (FileWriter fw = new FileWriter(filePath, true);
             BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(content);
            bw.newLine();
        } catch (IOException ignored) {}
    }
}

思考:虽然 Java 代码更长,但它具备更好的异常处理、类型安全和跨平台能力。而 Shell 脚本更适合在 Linux 环境中做轻量级、高效率的系统操作。

十二、进阶技巧:嵌套判断与复杂逻辑

有时我们需要在判断内部再进行判断,形成嵌套结构。

#!/bin/bash

read -p "是否继续安装? (y/n): " confirm
if [ "$confirm" = "y" ]; then
    read -p "是否备份现有数据? (y/n): " backup
    if [ "$backup" = "y" ]; then
        echo "💾 正在备份..."
        # 执行备份命令
    fi
    echo "📥 开始安装..."
    # 执行安装命令
elif [ "$confirm" = "n" ]; then
    echo "✋ 用户取消安装"
    exit 0
else
    echo "❓ 请输入 y 或 n"
    exit 1
fi

十三、调试技巧与日志记录

在复杂脚本中,合理输出调试信息非常重要。

13.1 使用set -x开启调试模式

#!/bin/bash
set -x  # 开启调试,显示每条命令执行过程

a=5
b=3
if [ $a -gt $b ]; then
    echo "a 大于 b"
fi

set +x  # 关闭调试

13.2 自定义日志函数

log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
}

# 使用
log_info "开始执行任务"
if some_command; then
    log_info "任务成功"
else
    log_error "任务失败"
fi

十四、总结

Shell 脚本中的条件判断看似简单,实则蕴含丰富的语法规则和最佳实践。从最基本的 if-then-else,到强大的 [[ ]]case 语句,再到结合函数、调试、日志的工程化脚本,每一步都需要细致打磨。

通过本文的学习,你应该已经掌握了:

✅ Shell 条件判断的基本语法
✅ 文件、字符串、数值的测试方法
[[ ]][ ] 的区别与选择
case 语句的灵活运用
✅ 与 Java 的对比理解
✅ 实战部署脚本编写
✅ 调试与日志技巧
✅ 常见陷阱规避

Shell 脚本是 Linux 世界的“胶水语言”,掌握它,你就能把各种命令、工具、程序粘合成强大的自动化流水线。无论你是 DevOps 工程师、系统管理员,还是后端开发者,Shell 条件判断都是你工具箱中必不可少的一环。

以上就是Linux Shell脚本中的条件判断语句的使用方法的详细内容,更多关于Linux Shell脚本条件判断语句用法的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文