【Linux】Shell 脚本中的条件判断语句

【Linux】Shell 脚本中的条件判断语句
在这里插入图片描述
👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Linux这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!

文章目录

Shell 脚本中的条件判断语句 🐚

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

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


一、什么是 Shell 条件判断?🎯

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

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

  • if ... then ... fi
  • if ... then ... else ... fi
  • if ... then ... elif ... else ... fi
  • case ... esac
  • 使用 [ ][[ ]]test 命令进行测试

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

#!/bin/bashif[1-eq1];thenecho"1 等于 1,条件成立 ✅"fi
💡 小贴士:在终端中,你可以随时用 echo $? 查看上一条命令的退出状态码。

二、基础 if 语句结构 🧱

2.1 单分支 if

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

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

示例:

#!/bin/bashread-p"请输入你的年龄: " age if[$age-ge18];thenecho"你已成年,可以投票 🗳️"fi

2.2 双分支 if-else

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

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

示例:

#!/bin/bashread-p"请输入分数 (0-100): " score if[$score-ge60];thenecho"🎉 恭喜你,及格了!"elseecho"😔 很遗憾,你需要补考。"fi

2.3 多分支 if-elif-else

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

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

示例:

#!/bin/bashread-p"请输入成绩等级 (A/B/C/D): " grade if["$grade"="A"];thenecho"优秀 🌟"elif["$grade"="B"];thenecho"良好 👍"elif["$grade"="C"];thenecho"及格 😅"elif["$grade"="D"];thenecho"不及格 ❌"elseecho"输入无效,请输入 A/B/C/D"fi

三、Java 对比示例 ☕

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

3.1 单分支 Java 示例

importjava.util.Scanner;publicclassAgeCheck{publicstaticvoidmain(String[] args){Scanner scanner =newScanner(System.in);System.out.print("请输入你的年龄: ");int age = scanner.nextInt();if(age >=18){System.out.println("你已成年,可以投票 🗳️");}}}

3.2 双分支 Java 示例

importjava.util.Scanner;publicclassScoreCheck{publicstaticvoidmain(String[] args){Scanner scanner =newScanner(System.in);System.out.print("请输入分数 (0-100): ");int score = scanner.nextInt();if(score >=60){System.out.println("🎉 恭喜你,及格了!");}else{System.out.println("😔 很遗憾,你需要补考。");}}}

3.3 多分支 Java 示例

importjava.util.Scanner;publicclassGradeCheck{publicstaticvoidmain(String[] args){Scanner scanner =newScanner(System.in);System.out.print("请输入成绩等级 (A/B/C/D): ");String grade = scanner.next();if(grade.equals("A")){System.out.println("优秀 🌟");}elseif(grade.equals("B")){System.out.println("良好 👍");}elseif(grade.equals("C")){System.out.println("及格 😅");}elseif(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/bashFILE="/etc/passwd"if[-e"$FILE"];thenecho"✅ 文件 $FILE 存在"elseecho"❌ 文件 $FILE 不存在"fiif[-f"$FILE"];thenecho"📄 它是一个普通文件"fiif[-r"$FILE"];thenecho"📖 你有读取权限"fi

4.2 字符串比较操作符

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

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

#!/bin/bashread-p"请输入用户名: " username if[-z"$username"];thenecho"⛔ 用户名不能为空"elif["$username"="admin"];thenecho"👑 欢迎管理员登录"elseecho"👋 欢迎用户 $username"fi

4.3 数值比较操作符

操作符含义
-eq等于
-ne不等于
-gt大于
-lt小于
-ge大于等于
-le小于等于
#!/bin/bashread-p"请输入一个数字: " num if[$num-gt100];thenecho"📈 数字大于 100"elif[$num-eq100];thenecho"🎯 正好是 100"elseecho"📉 小于 100"fi

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

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

  • 支持正则匹配 =~
  • 支持逻辑运算符 &&||
  • 不需要对变量加引号(但仍推荐)
  • 支持模式匹配(通配符)

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

#!/bin/bashread-p"请输入邮箱: " email if[[$email=~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]];thenecho"📧 邮箱格式正确"elseecho"❌ 邮箱格式错误"fi

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

#!/bin/bashread-p"请输入年龄: " age read-p"请输入城市: " city if[[$age-ge18&&"$city"=="北京"]];thenecho"🎉 北京地区的成年人,可申请居住证"elif[[$age-lt18||"$city"!="北京"]];thenecho"📌 不符合条件"fi
📌 推荐:在 Bash 脚本中优先使用 [[ ]],除非你需要兼容老式 Shell(如 dash、sh)。

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

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

6.1 基本语法

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

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

#!/bin/bashecho"请选择操作:"echo"1) 查看系统信息"echo"2) 查看磁盘使用"echo"3) 查看内存使用"echo"4) 退出"read-p"请输入选项 [1-4]: " choice case$choicein1)echo"💻 系统信息:"uname-a;;2)echo"💽 磁盘使用情况:"df-h;;3)echo"🧠 内存使用情况:"free-h;;4)echo"👋 再见!"exit0;; *)echo"❌ 无效选项,请输入 1-4";;esac

6.3 支持通配符和多个模式

#!/bin/bashread-p"请输入文件扩展名 (如 .txt, .jpg): " ext case$extin .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/bashping-c1 google.com >/dev/null 2>&1&&echo"🌐 网络通畅"||echo"🚫 网络不通"

7.2 在 if 中组合条件

#!/bin/bashread-p"请输入用户名: " user read-p"请输入密码: " pass if[-n"$user"]&&[-n"$pass"];thenecho"✅ 用户名和密码都不为空"elseecho"❌ 用户名或密码为空"fi

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

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

八、常见陷阱与最佳实践 🚧

8.1 变量未加引号导致错误

# ❌ 错误示范:当 name 为空或含空格时会报错if[$name="Alice"];then... # ✅ 正确做法:始终加双引号if["$name"="Alice"];then... 

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

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

8.3 忘记空格

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

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

#!/bin/basha=5b=3if(( a > b ));thenecho"$a 大于 $b"fi# 也可以直接计算if(( a + b >7));thenecho"两数之和大于 7"fi
🎯 提示:(( )) 是 Bash 的算术扩展,适合纯数学运算,不支持字符串。

九、结合函数与条件判断 🧩

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

#!/bin/bash# 判断是否为偶数is_even(){localnum=$1if(( num %2==0));thenreturn0# trueelsereturn1# falsefi}read-p"请输入一个数字: " n if is_even $n;thenecho"🔢 $n 是偶数"elseecho"🔢 $n 是奇数"fi

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

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

#!/bin/bashAPP_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 不存在,无法记录日志"exit1fi# 检查部署目录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"];thenBACKUP_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 不存在,部署终止"exit1fi# 复制新包 log "🚚 部署新版本..."cp"$NEW_JAR""$DEPLOY_DIR/app.jar"# 设置权限chmod +x "$DEPLOY_DIR/app.jar"# 检查 Java 是否安装if!command-vjava&> /dev/null;then log "❌ Java 未安装,无法启动应用"exit1fi# 启动应用cd"$DEPLOY_DIR"nohupjava-jar app.jar > app.log 2>&1&PID=$! log "✅ 应用启动成功,PID: $PID"sleep3# 检查进程是否存活ifps-p$PID> /dev/null;then log "🟢 应用运行中"else log "🔴 应用启动失败,请检查日志"exit1fi log "🎉 部署完成!"

十一、流程图可视化 📊

下面用 mermaid 图表展示上述部署脚本的核心判断流程:

开始部署

/var/log 存在?

记录错误并退出

部署目录存在?

创建部署目录

备份目录存在?

创建备份目录

旧版本存在?

备份旧版本

新版本包存在?

记录错误并退出

复制新包并设置权限

Java 是否安装?

记录错误并退出

启动应用

进程是否存活?

记录启动失败

部署成功🎉


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

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

importjava.io.*;importjava.time.LocalDateTime;importjava.time.format.DateTimeFormatter;publicclassDeployManager{privatestaticfinalStringAPP_NAME="myapp";privatestaticfinalStringDEPLOY_DIR="/opt/"+APP_NAME;privatestaticfinalStringBACKUP_DIR="/backup/"+APP_NAME;privatestaticfinalStringLOG_FILE="/var/log/deploy.log";publicstaticvoidmain(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();}}privatestaticvoidlog(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);}privatestaticbooleanisDirectoryExists(String path){File dir =newFile(path);return dir.exists()&& dir.isDirectory();}privatestaticvoidcreateDirectoryIfNotExists(String path)throwsIOException{File dir =newFile(path);if(!dir.exists()){if(dir.mkdirs()){log("🏗️ 创建目录: "+ path);}}}privatestaticvoidbackupOldVersion(String filePath,String backupDir)throwsIOException{File file =newFile(filePath);if(file.exists()){String backupName ="app_"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))+".jar";String backupPath = backupDir +"/"+ backupName;copyFile(filePath, backupPath);log("📦 备份旧版本到: "+ backupPath);}}privatestaticvoidcopyFile(String src,String dest)throwsIOException{File source =newFile(src);File destination =newFile(dest);// 简化实现,实际应使用 Files.copy()if(source.exists()){// 模拟复制 destination.createNewFile();}}privatestaticbooleanisFileExists(String path){returnnewFile(path).exists();}privatestaticbooleanisCommandAvailable(String command){try{Process p =Runtime.getRuntime().exec(newString[]{"which", command});return p.waitFor()==0;}catch(Exception e){returnfalse;}}privatestaticProcessstartApplication(String dir)throwsIOException{ProcessBuilder pb =newProcessBuilder("java","-jar","app.jar"); pb.directory(newFile(dir)); pb.redirectOutput(newFile(dir +"/app.log")); pb.redirectErrorStream(true);return pb.start();}privatestaticbooleanisProcessAlive(Process process){try{ process.exitValue();returnfalse;// 已退出}catch(IllegalThreadStateException e){returntrue;// 仍在运行}}privatestaticvoidappendToFile(String filePath,String content){try(FileWriter fw =newFileWriter(filePath,true);BufferedWriter bw =newBufferedWriter(fw)){ bw.write(content); bw.newLine();}catch(IOException ignored){}}}
🧠 思考:虽然 Java 代码更长,但它具备更好的异常处理、类型安全和跨平台能力。而 Shell 脚本更适合在 Linux 环境中做轻量级、高效率的系统操作。

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

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

#!/bin/bashread-p"是否继续安装? (y/n): " confirm if["$confirm"="y"];thenread-p"是否备份现有数据? (y/n): " backup if["$backup"="y"];thenecho"💾 正在备份..."# 执行备份命令fiecho"📥 开始安装..."# 执行安装命令elif["$confirm"="n"];thenecho"✋ 用户取消安装"exit0elseecho"❓ 请输入 y 或 n"exit1fi

十四、调试技巧与日志记录 🐞

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

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

#!/bin/bashset-x# 开启调试,显示每条命令执行过程a=5b=3if[$a-gt$b];thenecho"a 大于 b"fiset +x # 关闭调试

14.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 编程,以下资源值得一看:

这些文档不仅涵盖条件判断,还包括循环、函数、参数处理、信号捕获等完整内容,助你成为 Shell 脚本高手!


十六、总结 🎓

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

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

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

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


🌈 最后送你一句话:
“Shell 脚本写得好,半夜运维不会吵。”
祝你在 Linux 的世界里,所向披靡,脚本无 Bug!🚀

📌 本文完,感谢阅读!如有疑问或建议,欢迎留言交流。


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

Read more

2026年3月18日 AI 每日动态

2026年3月18日 AI 每日动态

1. 【AI Coding 工具】Claude Code 终于有了"长期记忆"——claude-mem 爆红 Claude Code 用起来顺手,但每次开新会话就像把同事的记忆清零——项目背景要重新交代,之前做过的决策一问三不知。现在有个叫 claude-mem 的开源插件彻底改变了这件事。 它的工作方式很直接:自动抓取每次会话里的工具调用记录(读了哪些文件、改了哪些代码、跑了什么命令),会话结束后用 AI 把这些信息压缩成结构化摘要,下次开工时自动注入进来。一万 Token 的操作记录,最终压缩到 500 Token 左右,同时还支持自然语言检索历史("上次那个 React 重复渲染是怎么解的?")。 目前已有超 3 万人收藏,宣称能节省 90% 的 Token

By Ne0inhk
人工智能:循环神经网络(RNN)与序列数据处理实战

人工智能:循环神经网络(RNN)与序列数据处理实战

循环神经网络(RNN)与序列数据处理实战 1.1 本章学习目标与重点 💡 学习目标:掌握循环神经网络的核心原理、经典变体结构,以及在文本序列任务中的实战开发流程。 💡 学习重点:理解 RNN 的循环计算机制,学会使用 TensorFlow/Keras 搭建基础 RNN 与 LSTM 模型,完成文本分类任务。 1.2 循环神经网络核心原理 1.2.1 为什么需要 RNN 💡 传统的前馈神经网络(如 CNN、全连接网络)的输入和输出是相互独立的。它们无法处理序列数据的上下文关联特性。 序列数据在现实中十分常见,比如自然语言文本、语音信号、时间序列数据等。这些数据的核心特点是,当前时刻的信息和之前时刻的信息紧密相关。 循环神经网络通过引入隐藏状态,可以存储历史信息,从而有效捕捉序列数据的上下文依赖关系。 1.2.2 RNN

By Ne0inhk
当人人都会用AI,你靠什么脱颖而出?

当人人都会用AI,你靠什么脱颖而出?

文章目录 * 一、引言:AI时代,你真的准备好了吗? * 二、脉向AI:连接AI与普通人的桥梁 * 2.1 什么是脉向AI? * 2.2 脉向AI的合作生态 * 2.3 为什么你需要关注脉向AI? * 三、本期重磅:《小Ni会客厅×AI熊厂长》深度对话 * 3.1 访谈背景 * 3.2 核心观点一:商业认知决定变现能力 * 3.3 核心观点二:个人标签决定商业价值 * 3.4 核心观点三:爆款策略决定起步速度 * 3.5 核心观点四:产品思维决定变现上限 * 四、从认知到行动:如何真正用AI赚到钱? * 4.1 建立正确的商业认知 * 4.2 找到你的70分领域

By Ne0inhk
Flutter 三方库 objectbox_generator — 自动化构建鸿蒙极速 NoSQL 数据库映射(适配鸿蒙 HarmonyOS Next ohos)

Flutter 三方库 objectbox_generator — 自动化构建鸿蒙极速 NoSQL 数据库映射(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter 三方库 objectbox_generator — 自动化构建鸿蒙极速 NoSQL 数据库映射(适配鸿蒙 HarmonyOS Next ohos) 在高性能移动应用开发中,本地数据的持久化存储效率往往是决定用户感知流畅度的木桶短板。传统的 SQLite 虽然结构化程度高,但在处理大规模对象关系映射(ORM)时,复杂的 SQL 拼接和反射解析往往会成为性能瓶颈。 ObjectBox 作为一个专为移动设备打造的、跨平台的超高速 NoSQL 数据库,已经成为了许多追求极致体验开发者的首选。而在 Flutter for OpenHarmony 开发中,配合 objectbox_generator,我们可以通过注解驱动的自动化流程,掌握这套高性能数据库的核心用法。 ⚠️ 鸿蒙适配现状提示:截至本文撰写时,ObjectBox 的 Dart 插件尚未提供官方的 OpenHarmony

By Ne0inhk