Linux Shell脚本中的条件判断语句的使用方法
作者:Jinkxs
在 Linux 系统中,Shell 脚本是自动化任务、系统管理、部署流程等场景下不可或缺的工具。而条件判断语句,则是 Shell 脚本实现逻辑控制的核心语法之一。无论是简单的文件存在性检查,还是复杂的多分支业务逻辑,都离不开 if、case、test、[[ ]] 和 [ ] 这些关键字与结构。
本文将从基础语法讲起,逐步深入到实际应用场景,并穿插 Java 代码作为对比示例,帮助你更好地理解 Shell 条件判断的本质。同时,我们也会借助 mermaid 图表 可视化展示逻辑流程,增强理解效果。
一、什么是 Shell 条件判断?
在 Shell 中,条件判断用于根据某个表达式或命令的“真假”来决定是否执行某段代码。其核心思想与几乎所有编程语言一致 —— “如果满足 A,则做 B;否则,做 C”。
Shell 的条件判断主要依赖以下几种结构:
if ... then ... fiif ... then ... else ... fiif ... then ... elif ... else ... ficase ... esac- 使用
[ ]、[[ ]]、test命令进行测试
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 中常用的测试方式有三种:
[ expression ]—— POSIX 标准,兼容性好[[ expression ]]—— Bash 扩展,功能更强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脚本条件判断语句用法的资料请关注脚本之家其它相关文章!
