Linux Shell腳本中的條件判斷語句的使用方法
在 Linux 系統(tǒng)中,Shell 腳本是自動化任務(wù)、系統(tǒng)管理、部署流程等場景下不可或缺的工具。而條件判斷語句,則是 Shell 腳本實(shí)現(xiàn)邏輯控制的核心語法之一。無論是簡單的文件存在性檢查,還是復(fù)雜的多分支業(yè)務(wù)邏輯,都離不開 if、case、test、[[ ]] 和 [ ] 這些關(guān)鍵字與結(jié)構(gòu)。
本文將從基礎(chǔ)語法講起,逐步深入到實(shí)際應(yīng)用場景,并穿插 Java 代碼作為對比示例,幫助你更好地理解 Shell 條件判斷的本質(zhì)。同時,我們也會借助 mermaid 圖表 可視化展示邏輯流程,增強(qiáng)理解效果。
一、什么是 Shell 條件判斷?
在 Shell 中,條件判斷用于根據(jù)某個表達(dá)式或命令的“真假”來決定是否執(zhí)行某段代碼。其核心思想與幾乎所有編程語言一致 —— “如果滿足 A,則做 B;否則,做 C”。
Shell 的條件判斷主要依賴以下幾種結(jié)構(gòu):
if ... then ... fiif ... then ... else ... fiif ... then ... elif ... else ... ficase ... esac- 使用
[ ]、[[ ]]、test命令進(jìn)行測試
Shell 不像高級語言那樣有布爾類型(true/false),它使用的是退出狀態(tài)碼(Exit Status):0 表示“真”,非 0 表示“假”。
#!/bin/bash
if [ 1 -eq 1 ]; then
echo "1 等于 1,條件成立 ?"
fi
小貼士:在終端中,你可以隨時用 echo $? 查看上一條命令的退出狀態(tài)碼。
二、基礎(chǔ) if 語句結(jié)構(gòu)
2.1 單分支 if
最簡單的形式是只判斷一個條件,滿足則執(zhí)行:
if [ condition ]; then
# 執(zhí)行語句
fi
示例:
#!/bin/bash
read -p "請輸入你的年齡: " age
if [ $age -ge 18 ]; then
echo "你已成年,可以投票 ???"
fi
2.2 雙分支 if-else
當(dāng)需要處理“否則”的情況時,使用 else:
if [ condition ]; then
# 條件為真時執(zhí)行
else
# 條件為假時執(zhí)行
fi
示例:
#!/bin/bash
read -p "請輸入分?jǐn)?shù) (0-100): " score
if [ $score -ge 60 ]; then
echo "?? 恭喜你,及格了!"
else
echo "?? 很遺憾,你需要補(bǔ)考。"
fi
2.3 多分支 if-elif-else
多個條件判斷使用 elif(即 else if):
if [ condition1 ]; then
# 執(zhí)行語句1
elif [ condition2 ]; then
# 執(zhí)行語句2
else
# 其他情況
fi
示例:
#!/bin/bash
read -p "請輸入成績等級 (A/B/C/D): " grade
if [ "$grade" = "A" ]; then
echo "優(yōu)秀 ??"
elif [ "$grade" = "B" ]; then
echo "良好 ??"
elif [ "$grade" = "C" ]; then
echo "及格 ??"
elif [ "$grade" = "D" ]; then
echo "不及格 ?"
else
echo "輸入無效,請輸入 A/B/C/D"
fi
三、Java 對比示例
為了幫助熟悉 Java 的開發(fā)者快速理解 Shell 的條件判斷,下面給出上述腳本對應(yīng)的 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("請輸入分?jǐn)?shù) (0-100): ");
int score = scanner.nextInt();
if (score >= 60) {
System.out.println("?? 恭喜你,及格了!");
} else {
System.out.println("?? 很遺憾,你需要補(bǔ)考。");
}
}
}
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("優(yōu)秀 ??");
} 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");
}
}
}
對比發(fā)現(xiàn):Shell 的語法更簡潔,但缺乏強(qiáng)類型和 IDE 支持,調(diào)試起來不如 Java 方便。但在系統(tǒng)級腳本中,Shell 更輕量高效。
四、條件測試命令詳解
Shell 中常用的測試方式有三種:
[ expression ]—— POSIX 標(biāo)準(zhǔn),兼容性好[[ expression ]]—— Bash 擴(kuò)展,功能更強(qiáng)test expression—— 與[ ]等價,常用于可讀性要求高的腳本
4.1 文件測試操作符
| 操作符 | 含義 |
|---|---|
-e file | 文件存在 |
-f file | 是普通文件 |
-d file | 是目錄 |
-r file | 可讀 |
-w file | 可寫 |
-x file | 可執(zhí)行 |
-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 "?? 你有讀取權(quán)限"
fi
4.2 字符串比較操作符
| 操作符 | 含義 |
|---|---|
str1 = str2 | 相等(注意是單個 =) |
str1 != str2 | 不相等 |
-z str | 字符串長度為 0(空) |
-n str | 字符串長度不為 0(非空) |
注意:在 [ ] 中,建議對變量加雙引號,避免空值導(dǎo)致語法錯誤。
#!/bin/bash
read -p "請輸入用戶名: " username
if [ -z "$username" ]; then
echo "? 用戶名不能為空"
elif [ "$username" = "admin" ]; then
echo "?? 歡迎管理員登錄"
else
echo "?? 歡迎用戶 $username"
fi
4.3 數(shù)值比較操作符
| 操作符 | 含義 |
|---|---|
-eq | 等于 |
-ne | 不等于 |
-gt | 大于 |
-lt | 小于 |
-ge | 大于等于 |
-le | 小于等于 |
#!/bin/bash
read -p "請輸入一個數(shù)字: " num
if [ $num -gt 100 ]; then
echo "?? 數(shù)字大于 100"
elif [ $num -eq 100 ]; then
echo "?? 正好是 100"
else
echo "?? 小于 100"
fi
五、[[ ]] 與 [ ] 的區(qū)別
雖然 [ ] 是 POSIX 標(biāo)準(zhǔn),廣泛兼容,但 [[ ]] 是 Bash 的擴(kuò)展語法,支持更多特性:
- 支持正則匹配
=~ - 支持邏輯運(yùn)算符
&&、|| - 不需要對變量加引號(但仍推薦)
- 支持模式匹配(通配符)
5.1 使用[[ ]]進(jìn)行正則匹配
#!/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 使用[[ ]]的邏輯運(yùn)算符
#!/bin/bash
read -p "請輸入年齡: " age
read -p "請輸入城市: " city
if [[ $age -ge 18 && "$city" == "北京" ]]; then
echo "?? 北京地區(qū)的成年人,可申請居住證"
elif [[ $age -lt 18 || "$city" != "北京" ]]; then
echo "?? 不符合條件"
fi
推薦:在 Bash 腳本中優(yōu)先使用 [[ ]],除非你需要兼容老式 Shell(如 dash、sh)。
六、case 語句:多分支選擇利器
當(dāng)面對多個固定選項(xiàng)時,case 語句比一連串 if-elif 更清晰、高效。
6.1 基本語法
case 變量 in
模式1)
命令1
;;
模式2)
命令2
;;
*)
默認(rèn)命令
;;
esac
6.2 實(shí)際示例:菜單選擇系統(tǒng)
#!/bin/bash
echo "請選擇操作:"
echo "1) 查看系統(tǒng)信息"
echo "2) 查看磁盤使用"
echo "3) 查看內(nèi)存使用"
echo "4) 退出"
read -p "請輸入選項(xiàng) [1-4]: " choice
case $choice in
1)
echo "?? 系統(tǒng)信息:"
uname -a
;;
2)
echo "?? 磁盤使用情況:"
df -h
;;
3)
echo "?? 內(nèi)存使用情況:"
free -h
;;
4)
echo "?? 再見!"
exit 0
;;
*)
echo "? 無效選項(xiàng),請輸入 1-4"
;;
esac
6.3 支持通配符和多個模式
#!/bin/bash
read -p "請輸入文件擴(kuò)展名 (如 .txt, .jpg): " ext
case $ext in
.txt|.md|.log)
echo "?? 這是一個文本文件"
;;
.jpg|.png|.gif)
echo "??? 這是一個圖片文件"
;;
.mp4|.avi|.mkv)
echo "?? 這是一個視頻文件"
;;
*)
echo "? 未知文件類型"
;;
esac
七、邏輯運(yùn)算符與組合條件
Shell 支持使用 &&(與)、||(或)、!(非)組合多個條件。
7.1 使用&&和||在命令中
# 成功執(zhí)行 command1 后才執(zhí)行 command2 command1 && command2 # command1 失敗才執(zhí)行 command2 command1 || command2
示例:
#!/bin/bash ping -c 1 google.com >/dev/null 2>&1 && echo "?? 網(wǎng)絡(luò)通暢" || echo "?? 網(wǎng)絡(luò)不通"
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
八、常見陷阱與最佳實(shí)踐
8.1 變量未加引號導(dǎo)致錯誤
# ? 錯誤示范:當(dāng) name 為空或含空格時會報錯 if [ $name = "Alice" ]; then ... # ? 正確做法:始終加雙引號 if [ "$name" = "Alice" ]; then ...
8.2 數(shù)值比較誤用字符串操作符
# ? 錯誤:使用 = 比較數(shù)值,結(jié)果可能不符合預(yù)期 if [ $a = $b ]; then ... # ? 正確:使用 -eq if [ $a -eq $b ]; then ...
8.3 忘記空格
# ? 錯誤:[ 和 ] 兩邊必須有空格 if [$a -eq $b]; then ... # ? 正確 if [ $a -eq $b ]; then ...
8.4 使用(( ))進(jìn)行算術(shù)判斷(僅限整數(shù))
#!/bin/bash
a=5
b=3
if (( a > b )); then
echo "$a 大于 $b"
fi
# 也可以直接計算
if (( a + b > 7 )); then
echo "兩數(shù)之和大于 7"
fi
提示:(( )) 是 Bash 的算術(shù)擴(kuò)展,適合純數(shù)學(xué)運(yùn)算,不支持字符串。
九、結(jié)合函數(shù)與條件判斷
將條件判斷封裝進(jìn)函數(shù),提高代碼復(fù)用性和可讀性。
#!/bin/bash
# 判斷是否為偶數(shù)
is_even() {
local num=$1
if (( num % 2 == 0 )); then
return 0 # true
else
return 1 # false
fi
}
read -p "請輸入一個數(shù)字: " n
if is_even $n; then
echo "?? $n 是偶數(shù)"
else
echo "?? $n 是奇數(shù)"
fi
十、實(shí)戰(zhàn)案例:自動化部署腳本
下面是一個模擬的部署腳本,包含多個條件判斷:
#!/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 "??? 創(chuàng)建部署目錄 $DEPLOY_DIR"
mkdir -p "$DEPLOY_DIR"
fi
# 檢查備份目錄
if [ ! -d "$BACKUP_DIR" ]; then
log "??? 創(chuàng)建備份目錄 $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
# 復(fù)制新包
log "?? 部署新版本..."
cp "$NEW_JAR" "$DEPLOY_DIR/app.jar"
# 設(shè)置權(quán)限
chmod +x "$DEPLOY_DIR/app.jar"
# 檢查 Java 是否安裝
if ! command -v java &> /dev/null; then
log "? Java 未安裝,無法啟動應(yīng)用"
exit 1
fi
# 啟動應(yīng)用
cd "$DEPLOY_DIR"
nohup java -jar app.jar > app.log 2>&1 &
PID=$!
log "? 應(yīng)用啟動成功,PID: $PID"
sleep 3
# 檢查進(jìn)程是否存活
if ps -p $PID > /dev/null; then
log "?? 應(yīng)用運(yùn)行中"
else
log "?? 應(yīng)用啟動失敗,請檢查日志"
exit 1
fi
log "?? 部署完成!"
十一、Java 中的類似部署邏輯對比 ??
下面是用 Java 實(shí)現(xiàn)類似部署邏輯的簡化版:
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("?? 應(yīng)用運(yùn)行中,PID: " + process.pid());
} else {
log("?? 應(yīng)用啟動失敗");
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("??? 創(chuàng)建目錄: " + 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);
// 簡化實(shí)現(xiàn),實(shí)際應(yīng)使用 Files.copy()
if (source.exists()) {
// 模擬復(fù)制
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; // 仍在運(yùn)行
}
}
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 環(huán)境中做輕量級、高效率的系統(tǒng)操作。
十二、進(jìn)階技巧:嵌套判斷與復(fù)雜邏輯
有時我們需要在判斷內(nèi)部再進(jìn)行判斷,形成嵌套結(jié)構(gòu)。
#!/bin/bash
read -p "是否繼續(xù)安裝? (y/n): " confirm
if [ "$confirm" = "y" ]; then
read -p "是否備份現(xiàn)有數(shù)據(jù)? (y/n): " backup
if [ "$backup" = "y" ]; then
echo "?? 正在備份..."
# 執(zhí)行備份命令
fi
echo "?? 開始安裝..."
# 執(zhí)行安裝命令
elif [ "$confirm" = "n" ]; then
echo "? 用戶取消安裝"
exit 0
else
echo "? 請輸入 y 或 n"
exit 1
fi
十三、調(diào)試技巧與日志記錄
在復(fù)雜腳本中,合理輸出調(diào)試信息非常重要。
13.1 使用set -x開啟調(diào)試模式
#!/bin/bash
set -x # 開啟調(diào)試,顯示每條命令執(zhí)行過程
a=5
b=3
if [ $a -gt $b ]; then
echo "a 大于 b"
fi
set +x # 關(guān)閉調(diào)試
13.2 自定義日志函數(shù)
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 "開始執(zhí)行任務(wù)"
if some_command; then
log_info "任務(wù)成功"
else
log_error "任務(wù)失敗"
fi
十四、總結(jié)
Shell 腳本中的條件判斷看似簡單,實(shí)則蘊(yùn)含豐富的語法規(guī)則和最佳實(shí)踐。從最基本的 if-then-else,到強(qiáng)大的 [[ ]] 和 case 語句,再到結(jié)合函數(shù)、調(diào)試、日志的工程化腳本,每一步都需要細(xì)致打磨。
通過本文的學(xué)習(xí),你應(yīng)該已經(jīng)掌握了:
? Shell 條件判斷的基本語法
? 文件、字符串、數(shù)值的測試方法
? [[ ]] 與 [ ] 的區(qū)別與選擇
? case 語句的靈活運(yùn)用
? 與 Java 的對比理解
? 實(shí)戰(zhàn)部署腳本編寫
? 調(diào)試與日志技巧
? 常見陷阱規(guī)避
Shell 腳本是 Linux 世界的“膠水語言”,掌握它,你就能把各種命令、工具、程序粘合成強(qiáng)大的自動化流水線。無論你是 DevOps 工程師、系統(tǒng)管理員,還是后端開發(fā)者,Shell 條件判斷都是你工具箱中必不可少的一環(huán)。
以上就是Linux Shell腳本中的條件判斷語句的使用方法的詳細(xì)內(nèi)容,更多關(guān)于Linux Shell腳本條件判斷語句用法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Shell中統(tǒng)計字符串中單詞的個數(shù)的幾種方法
今天小編就為大家分享一篇關(guān)于Shell中統(tǒng)計字符串中單詞的個數(shù)的幾種方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
Shell腳本實(shí)現(xiàn)FTP自動上傳和下載文件
本文主要介紹了Shell腳本實(shí)現(xiàn)FTP自動上傳和下載文件,主要內(nèi)容包括批量下載腳本代碼、下載單個文件腳本代碼、登錄FTP實(shí)現(xiàn)上傳文件功能、上傳單個文件腳本代碼等2023-08-08
實(shí)現(xiàn)MySQL定時批量檢查表repair和優(yōu)化表optimize table的shell腳本
這篇文章主要介紹了實(shí)現(xiàn)MySQL定時批量檢查表repair和優(yōu)化表optimize table的shell腳本,非常實(shí)用,需要的朋友可以參考下2014-07-07
Shell編程之/bin/bash和/bin/sh的區(qū)別淺析
在redhat的系統(tǒng)中,sh是一個到bash的軟鏈接,但是如果運(yùn)行如下程序會產(chǎn)生不一樣的結(jié)果,下面這篇文章主要給大家介紹了關(guān)于Shell編程之/bin/bash和/bin/sh的區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-07-07
linux?shell字符串截取的詳細(xì)總結(jié)(實(shí)用!)
在開發(fā)的時候經(jīng)常會自行寫一些小的腳本,其中就用到截取字符串的操作,這篇文章主要給大家介紹了關(guān)于linux?shell字符串截取的詳細(xì)方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07

