---
# System prepended metadata

title: '[shell] if-else-fi'
tags: [shell script, linux, OS, if-else, OS / Ubuntu / shell script, Ubuntu]

---

[shell] if-else-fi
===
###### tags: `OS / Ubuntu / shell script`
###### tags: `OS`, `Ubuntu`, `linux`, `shell script`, `if-else`

<br>

[TOC]

<br>


## 常見的 `test` / `[ ... ]` / `[[ ... ]]` 的 **if 判斷式**
> by gpt-5.2 (2025/10/22)

> 小提醒：`[ ... ]` 是傳統 test，`[[ ... ]]` 是 bash 的加強版（比較不容易被字串空白、萬用字元干擾）。一般建議在 bash 腳本優先用 `[[ ... ]]`。

---

### 檔案 / 目錄判斷

| 判斷式                   | 意義                     | 範例                          |
| --------------------- | ---------------------- | --------------------------- |
| `-e path`             | 存在（不管檔案或目錄）            | `[[ -e /etc/hosts ]]`       |
| `-f path`             | 是一般檔案 (regular file)   | `[[ -f ./a.txt ]]`          |
| `-d path`             | 是目錄                    | `[[ -d /var/log ]]`         |
| `-L path` 或 `-h path` | 是符號連結                  | `[[ -L /bin/sh ]]`          |
| `-r path`             | 可讀                     | `[[ -r ./a.txt ]]`          |
| `-w path`             | 可寫                     | `[[ -w ./a.txt ]]`          |
| `-x path`             | 可執行                    | `[[ -x ./script.sh ]]`      |
| `-s path`             | 檔案大小 > 0               | `[[ -s ./a.txt ]]`          |
| `-b path`             | block device（磁碟類）      | `[[ -b /dev/sda ]]`         |
| `-c path`             | char device（終端類）       | `[[ -c /dev/tty ]]`         |
| `-p path`             | named pipe (FIFO)      | `[[ -p /tmp/fifo ]]`        |
| `-S path`             | socket                 | `[[ -S /run/docker.sock ]]` |
| `path1 -nt path2`     | path1 比 path2 新（mtime） | `[[ a -nt b ]]`             |
| `path1 -ot path2`     | path1 比 path2 舊（mtime） | `[[ a -ot b ]]`             |
| `path1 -ef path2`     | 同一個檔案（同 inode）         | `[[ a -ef b ]]`             |

---

### 字串判斷（最常用的 -z / -n 在這）

| 判斷式          | 意義                    | 範例                       |
| ------------ | --------------------- | ------------------------ |
| `-z s`       | 字串長度為 0（空字串）（z: zero） | `[[ -z "$NAME" ]]`       |
| `-n s`       | 字串長度非 0（非空）           | `[[ -n "$NAME" ]]`       |
| `s1 = s2`    | 相等                    | `[[ "$A" = "ok" ]]`      |
| `s1 != s2`   | 不相等                   | `[[ "$A" != "ok" ]]`     |
| `s1 < s2`    | 以字典序比較（在 `[[ ]]` 才安全） | `[[ "$A" < "$B" ]]`      |
| `s1 > s2`    | 以字典序比較（在 `[[ ]]` 才安全） | `[[ "$A" > "$B" ]]`      |
| `s =~ regex` | 正規表示式匹配（bash `[[ ]]`） | `[[ "$A" =~ ^[0-9]+$ ]]` |

- ### 更多 `s =~ regex` 範例
    - `[[ ! "$file" =~ -scaler\.yaml$ ]]`

---

### 整數判斷（數值比較）

| 判斷式   | 意義   | 範例                 |
| ----- | ---- | ------------------ |
| `-eq` | 等於   | `[[ "$N" -eq 3 ]]` |
| `-ne` | 不等於  | `[[ "$N" -ne 3 ]]` |
| `-gt` | 大於   | `[[ "$N" -gt 3 ]]` |
| `-ge` | 大於等於 | `[[ "$N" -ge 3 ]]` |
| `-lt` | 小於   | `[[ "$N" -lt 3 ]]` |
| `-le` | 小於等於 | `[[ "$N" -le 3 ]]` |

---

### 邏輯組合（AND / OR / NOT）

| 寫法                 | 意義           | 範例                               |
| ------------------ | ------------ | -------------------------------- |
| `! cond`           | 反相           | `[[ ! -f a.txt ]]`               |
| `cond1 && cond2`   | AND（建議用這個）   | `[[ -n "$A" ]] && [[ -f "$P" ]]` |
| `cond1 \|\| cond2` | OR（建議用這個）    | `[[ -d a ]] \|\| [[ -d b ]]`     |
| `cond1 -a cond2`   | AND（舊式，不太建議） | `[ -n "$A" -a -f "$P" ]`         |
| `cond1 -o cond2`   | OR（舊式，不太建議）  | `[ -d a -o -d b ]`               |

---

### 變數存在與否（bash 常用技巧）

| 判斷式               | 意義                         | 範例                    |
| ----------------- | -------------------------- | --------------------- |
| `-v VAR`          | 變數已宣告（bash `[[ ]]`）        | `[[ -v PATH ]]`       |
| `${VAR+x}`        | VAR 有設定（含空字串也算）            | `[[ -n "${VAR+x}" ]]` |
| `${VAR:-default}` | VAR 未設或為空 -> default       | `X="${VAR:-foo}"`     |
| `${VAR-default}`  | VAR 未設 -> default（空字串不算未設） | `X="${VAR-foo}"`      |

---

### 最常見的 if 寫法範例（可直接套）

```bash
if [[ -d "$DIR" && -n "$NAME" ]]; then
  echo "ok"
else
  echo "not ok"
fi
```


<br>

---


## if-condition

### `if [ -t 0 ]; then`
- ### gpt-5 (2025/10/16)
    `[ -t 0 ]` 是在**測試「檔案描述元 0（stdin）」是不是接在一個終端機（TTY）」**。

    * `-t`：test 的條件，表示「該檔案描述元是否為終端機」
    * `0`：stdin（標準輸入）。`1` 是 stdout，`2` 是 stderr

    所以：

    ```bash
    if [ -t 0 ]; then
      # 目前是在互動式終端機執行（可以安全地 read 提示）
    else
      # 非互動（例如被 pipe、重導向、CI），不要等待使用者輸入
    fi
    ```

    ### 小範例

    * 直接跑腳本（互動）：`-t 0` 為真 → 會出現 `read -p` 提示
    * 管線/重導向（非互動）：`echo x | ./script.sh` 或 `./script.sh < /dev/null` → `-t 0` 為假 → 不會提示

    > 延伸：也常見用 `[ -t 1 ]` 檢查 stdout 是否是 TTY（決定要不要上色輸出）。


<br>

---

## 案例學習
### `if <command>; then ... else ... fi`
- ### gpt-5 (2025/09/03)
    ```bash
    if helm -n "${SLINKY_NAME}" status "${SLINKY_NAME}" >/dev/null 2>&1; then
      ...
    else
      ...
    fi
    ```
    這個 `if` 條件是在「用指令的**退出碼**(exit status)當布林值」：
    
    ### 執行順序與判斷邏輯

    1. **先執行指令**：`helm -n "$SLINKY_NAME" status "$SLINKY_NAME"`

       * 這會查詢該命名空間裡是否有同名的 Helm release。
    2. **輸出重導**：`>/dev/null 2>&1`

       * `>/dev/null`：把**標準輸出**(stdout)丟掉。
       * `2>&1`：把**標準錯誤**(stderr)也重導到目前的 stdout（此時已丟到 `/dev/null`），所以畫面上不會看到任何輸出。
    3. **if 判斷依據**：shell 以**退出碼**判斷真假

       * **0**（成功）→ 進入 `then` 區塊。
       * **非 0**（失敗）→ 進入 `else` 區塊。

    ---
    
    ### 小重點（通用規則）

    * `if <command>; then ... else ... fi` 會用**指令的退出碼**判斷真假（0 為真、非 0 為假）。
    * `>/dev/null 2>&1` 把**標準輸出與錯誤**都丟棄，避免干擾畫面。
    * 想記錄錯誤碼可用：

      ```bash
      if some_cmd; then
        echo OK
      else
        rc=$?
        echo "FAIL with code: $rc"
      fi
      ```

<br>

---

### 用 `ls`：檢查檔案是否存在
- ### gpt-5 (2025/09/03)
    ```bash
    #!/usr/bin/env bash
    # Demo 1: branch by success/failure of a command (ls)

    if ls /etc/passwd >/dev/null 2>&1; then
      echo "[IF] /etc/passwd exists."
    else
      echo "[ELSE] /etc/passwd not found."
    fi
    ```

    * 成功：找到檔案 → 退出碼 0 → 進 IF
    * 失敗：找不到檔案 → 非 0 → 進 ELSE

<br>

---

### 用 `grep -q`：檢查文字是否出現
- ### gpt-5 (2025/09/03)
    ```bash
    #!/usr/bin/env bash
    # Demo 2: grep succeeds (exit 0) if pattern is found

    if grep -q "^root:" /etc/passwd >/dev/null 2>&1; then
      echo "[IF] Found 'root' user."
    else
      echo "[ELSE] 'root' user not found."
    fi
    ```

    * 成功：找到關鍵字 → 0 → IF
    * 失敗：沒找到 / 讀檔錯誤 → 非 0 → ELSE

<br>

---

### 用 `command -v`：檢查指令是否已安裝
- ### gpt-5 (2025/09/03)
    ```bash
    #!/usr/bin/env bash
    # Demo 3: check if a command is available (e.g., git)

    if command -v git >/dev/null 2>&1; then
      echo "[IF] 'git' is installed."
    else
      echo "[ELSE] 'git' is NOT installed."
    fi
    ```

    * 成功：系統路徑找到該指令 → 0 → IF
    * 失敗：找不到 → 非 0 → ELSE


<br>

### 用「命令列參數」與「互動輸入」教學
- ### gpt-5 (2025/09/03)

    下面給你兩個最常見的 if-else 範例：用「命令列參數」與用「互動輸入」。

    ### 1) 用參數判斷（沒有名字就顯示用法）

    **檔名：** `hello.sh`

    ```bash
    #!/usr/bin/env bash
    # Simple if-else "Hello" using an argument

    # If no name is given, show usage and exit with code 1
    if [[ -z "$1" ]]; then
      echo "Usage: $0 <name>"
      exit 1
    else
      echo "Hello, $1!"
    fi
    ```

    **執行步驟**

    ```bash
    chmod +x hello.sh
    ./hello.sh           # 會顯示用法並以狀態碼 1 結束
    ./hello.sh Alice     # 輸出：Hello, Alice!
    echo $?              # 查看上一個指令的退出碼
    ```

    **重點說明**

    * `[[ -z "$1" ]]`：判斷第 1 個參數是否為空字串。
    * `$0`：腳本自身檔名（在用法提示很好用）。
    * `exit 1`：用非 0 代表「發生可預期的小錯」(例如使用方式不正確)。

    ---

    ### 2) 用互動輸入判斷（空白就用預設 world）

    **檔名：** `hello_interactive.sh`

    ```bash
    #!/usr/bin/env bash
    # Simple if-else "Hello" using interactive input

    read -rp "Your name? " name
    # If name is empty, default to 'world'; else greet the given name
    if [[ -z "$name" ]]; then
      echo "Hello, world!"
    else
      echo "Hello, $name!"
    fi
    ```

    **執行**

    ```bash
    chmod +x hello_interactive.sh
    ./hello_interactive.sh
    # 直接 Enter -> Hello, world!
    # 輸入 Alice -> Hello, Alice!
    ```

    ---

    ### （加碼）if-elif-else 範例

    ```bash
    #!/usr/bin/env bash
    name="$1"

    if [[ -z "$name" ]]; then
      echo "Usage: $0 <name>"
      exit 1
    elif [[ "$name" == "world" ]]; then
      echo "Hello, World!"
    else
      echo "Hello, $name!"
    fi
    ```

    > 以上就是 Ubuntu 上最精簡實用的 if-else「Hello, World」範例：一個用參數、一個用互動輸入，再加一個 `elif` 版本，足夠當快速入門模板。

<br>

---

<br>

## 測試理解

### 解讀：`if [ -n "$grp" ] && [ -d "/work/$grp" ] && [ -x "/work/$grp" ]; then`
> by gpt-5.2 (2026/01/20)

這行是在做 **三個條件都成立才進入 if**（中間用 `&&` 串起來，代表 AND）：

```sh
if [ -n "$grp" ] && [ -d "/work/$grp" ] && [ -x "/work/$grp" ]; then
```

逐段意思是：

1. `[ -n "$grp" ]`

   * `-n` = *string length is non-zero*
   * 也就是：`$grp` **不是空字串**（有抓到 group 名稱）
   * 例如 `grp=$(id -gn)` 失敗或回空，就不要亂用 `/work/` 去 cd。

2. `[ -d "/work/$grp" ]`

   * `-d` = *is a directory*
   * 也就是：`/work/<group>` **這個路徑存在且是目錄**。

3. `[ -x "/work/$grp" ]`

   * `-x` = *is executable*（對目錄來說代表**有「進入/搜尋」權限**，也就是 execute permission）
   * 也就是：你對 `/work/<group>` **有權限進去 cd**。
   * 目錄的 `x` 權限很重要：沒有 `x`，就算你 `ls` 看到目錄也不能 `cd` 進去。

所以整句合起來就是：

> 只有當 `grp` 有值，而且 `/work/$grp` 這個目錄存在，且你有權限進入它時，才去 `cd "/work/$grp"`；否則就停留在 `/work`。

補充一個小例子（直覺理解）：

* `grp=""` → 第一個條件失敗 → 不進 if
* `/work/dev` 不存在 → 第二個條件失敗 → 不進 if
* `/work/dev` 存在但權限是 `drw-r-----`（沒有 x）→ 第三個條件失敗 → 不進 if

如果你希望「有目錄就嘗試 cd，不行就算了」也可以簡化成一行：

```sh
[ -n "$grp" ] && cd "/work/$grp" >/dev/null 2>&1 || true
```

只是我原本那種寫法比較清楚也比較少做無謂嘗試。


<br>

---

<br>

## `[ ];`  vs `[[ ]];`
- ### Question
    `if [ "$ENABLED" = true ]; then`
    和
    `if [[ "$ENABLED" = true ]]; then`
    差在哪裡？
- ### gpt-5 (2025/10/31)
    短答：在你這段**有加引號、單純比對文字**的情況下，兩者效果一樣；但本質不同、行為在其他情境會分岐。

    ### 差異重點

    * `[ … ]` 是 **POSIX `test` 指令**（外部/內建指令），**可攜**到 /bin/sh。
    * `[[ … ]]` 是 **Bash/Zsh 關鍵字**，**不可攜**到純 POSIX sh，但更安全、功能也更強。

    ### 行為差異

    1. **字詞分割 / 萬用字元展開**

       * `[ … ]`：會先做字詞分割與萬用字元展開，所以變數**必須加引號**。
       * `[[ … ]]`：不做字詞分割與萬用字元展開，未加引號也較安全。

       ```bash
       var=
       [ $var = true ]        # 可能出現：unary operator expected
       [[ $var = true ]]      # 不會報錯
       ```

    2. **比對語意**

       * 在 `[[ … ]]` 裡，`=`/`==` 右側**未加引號時**支援 **glob 樣式比對**：

         ```bash
         [[ $x = t* ]]        # x 以 't' 開頭 → true
         [ "$x" = t* ]        # 這裡是把字面量 t* 拿來比對，不是樣式
         ```
       * `[[ … ]]` 還有 **正則**：`=~`（右側**不要加引號**）

         ```bash
         [[ $x =~ ^t(rue|RUE)$ ]]
         ```

    3. **字串大小比較符號 `<` `>`**

       * `[ … ]` 中 `<`、`>` 會被當成**重導向**，要跳脫：

         ```bash
         [ "$a" \< "$b" ]
         ```
       * `[[ … ]]` 可直接用：

         ```bash
         [[ $a < $b ]]
         ```

    4. **條件組合**

       * `[[ … ]]` 可直接用 `&&`、`||`、`()` 分組：

         ```bash
         [[ cond1 && (cond2 || cond3) ]]
         ```
       * `[ … ]` 通常要拆成多個 `[ ]` 配合 `&&` / `||`。

    5. **變數存在性**

       * `[[ … ]]` 支援 `-v var`（Bash 4.2+）檢查變數是否已設定：

         ```bash
         [[ -v ENABLED ]]
         ```

    ### 結論與建議

    * 你的例子：

      ```bash
      if [ "$ENABLED" = true ]; then
      # 與
      if [[ "$ENABLED" = true ]]; then
      ```

      兩者**結果相同**（因為已加引號、純文字比對）。
    * 若腳本標頭是 `#!/usr/bin/env bash`，偏好用 `[[ … ]]`，較安全且可用樣式/正則。
    * 若需要 **POSIX 可攜**（/bin/sh 也能跑），就用 `[ … ]`，並**確實為變數加引號**。


<br>

---

## `[ … ]`：會先做字詞分割與萬用字元展開，所以變數必須加引號。
> unary operator expected,

    
- ### gpt-5 (round-1)(2025/10/31)
    這個點超常踩坑的，我給你幾個最典型的。

    ### 1. 空字串導致「一元運算子預期」

    ```bash
    name=""
    # 沒有引號
    if [ $name = foo ]; then
      echo ok
    fi
    ```

    執行時可能看到：

    ```text
    [: =: unary operator expected
    ```

    因為沒引號，`[ ... ]` 裡實際變成：

    ```bash
    [ = foo ]
    ```

    前面少了一個運算元，就報錯。改成：

    ```bash
    if [ "$name" = foo ]; then
      echo ok
    fi
    ```

    就沒事。

    ---

    ### 2. 有空白的值被「拆成多個字」

    ```bash
    file="my doc.txt"

    # 沒有引號
    [ -f $file ]
    ```

    展開後會變成：

    ```bash
    [ -f my doc.txt ]
    ```

    對 shell 來說這是 4 個字：`[`, `-f`, `my`, `doc.txt`, 會判斷錯或報錯。正確寫法：

    ```bash
    [ -f "$file" ]
    ```

    ---

    ### 3. 萬用字元被當成 pattern 去展開

    ```bash
    pattern="*.log"

    # 沒有引號
    [ $pattern = "*.log" ]
    ```

    假設目前目錄剛好有 `a.log b.log`，那 `$pattern` 會先被展開成

    ```text
    a.log b.log
    ```

    整個式子就變成類似：

    ```bash
    [ a.log b.log = "*.log" ]
    ```

    語法就壞掉了。
    正確寫法：

    ```bash
    [ "$pattern" = "*.log" ]
    ```

    ---

    ### 4. 路徑裡剛好有星號的情況（比較少見但很壞）

    ```bash
    path="/tmp/*/data"
    [ -d $path ]     # 糟糕，會在 /tmp 下展開
    [ -d "$path" ]   # 保留原字面
    ```

    ---

    ### 對照：為什麼 `[[ ... ]]` 比較不容易出事

    ```bash
    name=""
    if [[ $name = foo ]]; then   # 即使沒加引號也不會被拆字
      echo ok
    fi
    ```

    因為 `[[ ... ]]` 裡不做 word splitting / pathname expansion，所以容錯高很多。

    所以那句話的重點就是：「**在 `[ ... ]` 裡，用變數幾乎都要加雙引號**」，這是寫 Shell 的肌肉記憶之一。

    <br>

    ---
    ---

- ### gpt-5 (round-2)(2025/10/31)
    重點是：**未加引號的變數會先被「字詞分割」與「萬用字元（globbing）展開」**，導致 `[ … ]` 看到的引數跟你想的不一樣。下面用最直觀的例子示範。

    ### 1) 字詞分割（spaces 造成）

    ```bash
    x="foo bar"
    [ $x = "foo bar" ] && echo OK || echo NG
    # bash: [: too many arguments   ← 被切成 5 個詞：[ , foo , = , foo , bar , ]
    ```

    ✔ 正確寫法（加引號）：

    ```bash
    [ "$x" = "foo bar" ] && echo OK || echo NG
    # OK
    ```

    **空字串也會出事：**

    ```bash
    x=
    [ $x = true ]
    # bash: [: =: unary operator expected
    ```

    ✔ 正確：

    ```bash
    [ "$x" = true ]   # 不會報錯，條件為假
    ```

    ---

    ### 2) 萬用字元展開（globbing）

    假設目錄下有 `a.txt b.txt`：

    ```bash
    pattern="*.txt"
    [ $pattern = "*.txt" ] && echo match || echo NG
    # bash: [: too many arguments   ← $pattern 被展成 a.txt b.txt 再分割
    ```

    ✔ 正確：

    ```bash
    [ "$pattern" = "*.txt" ] && echo match || echo NG
    # match
    ```

    或是測檔案存在時也會被影響：

    ```bash
    file="*.txt"
    [ -e $file ] && echo exists || echo not
    # 可能變成：-e a.txt b.txt → too many arguments（或只剩第一個，語意錯）
    ```

    ✔ 正確：

    ```bash
    [ -e "$file" ] && echo exists || echo not
    ```

    ---

    ### 3) 為什麼 `[[ … ]]` 比較安全

    `[[ … ]]` **不做字詞分割與萬用字元展開**（除非你刻意用模式比對），所以下面不會報錯：

    ```bash
    x="foo bar"
    [[ $x = "foo bar" ]] && echo OK    # OK（即使沒加引號）
    ```

    ---

    **結論**：

    * 在 `[ … ]` 裡：**一律為變數加引號**（`"$var"`）以避免分割與展開。
    * 若腳本使用 Bash/Zsh，傾向用 `[[ … ]]`，更安全，也有額外功能（模式/正則）。

<br>

{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}