【Linux】Shell 脚本中的条件判断语句
👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Linux这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
- Shell 脚本中的条件判断语句 🐚
Shell 脚本中的条件判断语句 🐚
在 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/bashif[1-eq1];thenecho"1 等于 1,条件成立 ✅"fi💡 小贴士:在终端中,你可以随时用 echo $? 查看上一条命令的退出状态码。二、基础 if 语句结构 🧱
2.1 单分支 if
最简单的形式是只判断一个条件,满足则执行:
if[ condition ];then# 执行语句fi示例:
#!/bin/bashread-p"请输入你的年龄: " age if[$age-ge18];thenecho"你已成年,可以投票 🗳️"fi2.2 双分支 if-else
当需要处理“否则”的情况时,使用 else:
if[ condition ];then# 条件为真时执行else# 条件为假时执行fi示例:
#!/bin/bashread-p"请输入分数 (0-100): " score if[$score-ge60];thenecho"🎉 恭喜你,及格了!"elseecho"😔 很遗憾,你需要补考。"fi2.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 中常用的测试方式有三种:
[ expression ]—— POSIX 标准,兼容性好[[ expression ]]—— Bash 扩展,功能更强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"📖 你有读取权限"fi4.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"fi4.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"❌ 邮箱格式错误"fi5.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 ;; *) 默认命令 ;;esac6.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";;esac6.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 编程,以下资源值得一看:
- Bash Guide for Beginners —— 适合初学者的全面指南
- Advanced Bash-Scripting Guide —— 深入探讨高级主题
- Google Shell Style Guide —— Google 官方 Shell 编码规范
这些文档不仅涵盖条件判断,还包括循环、函数、参数处理、信号捕获等完整内容,助你成为 Shell 脚本高手!
十六、总结 🎓
Shell 脚本中的条件判断看似简单,实则蕴含丰富的语法规则和最佳实践。从最基本的 if-then-else,到强大的 [[ ]] 和 case 语句,再到结合函数、调试、日志的工程化脚本,每一步都需要细致打磨。
通过本文的学习,你应该已经掌握了:
✅ Shell 条件判断的基本语法
✅ 文件、字符串、数值的测试方法
✅ [[ ]] 与 [ ] 的区别与选择
✅ case 语句的灵活运用
✅ 与 Java 的对比理解
✅ 实战部署脚本编写
✅ 调试与日志技巧
✅ 常见陷阱规避
Shell 脚本是 Linux 世界的“胶水语言”,掌握它,你就能把各种命令、工具、程序粘合成强大的自动化流水线。无论你是 DevOps 工程师、系统管理员,还是后端开发者,Shell 条件判断都是你工具箱中必不可少的一环。
🌈 最后送你一句话:
“Shell 脚本写得好,半夜运维不会吵。”
祝你在 Linux 的世界里,所向披靡,脚本无 Bug!🚀
📌 本文完,感谢阅读!如有疑问或建议,欢迎留言交流。
🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨