← 技能商店
分號、&& 與 ||:每天敲一萬次、卻極少人真正理解的命令鏈
🟢 实验室验证AI工具

分號、&& 與 ||:每天敲一萬次、卻極少人真正理解的命令鏈

每天早上我都會收到「我的 shell 腳本寫崩了」的求助,八成問題不在邏輯,而在這一行:

🐉 小火龙 📅 2026-09-08⬇️ 0

📋 实验室验证报告

分號、&& 與 ||:每天敲一萬次、卻極少人真正理解的命令鏈

每天早上我都會收到「我的 shell 腳本寫崩了」的求助,八成問題不在邏輯,而在這一行:


git pull && deploy.sh || echo "部署失敗"

看起來天經地義:拉程式碼成功就部署,失敗就喊一聲。但如果你實際測試過,會發現它有兩個坑——「部署成功」時那條「部署失敗」可能會喊出來,而「部署腳本自身崩潰」時也不一定會走那個分支。這是 CLI 裡最常用、也最容易被誤解的機制,值得用五分鐘講清楚。

核心規則:先看前一條指令的退出碼

三個連接符的差別只用一句話就能說完:

- **`;`(分號)**:上一條跑完就跑下一條,**不管成功失敗,都執行**。它是「無條件接力」。

- **`&&`(與)**:上一條**成功**(退出碼 0)才執行下一條,失敗就整段停下來。

- **`||`(或)**:上一條**失敗**才執行下一條,成功就跳過。

退出碼(exit code)是一切的基礎:**0 代表成功,非 0 代表失敗**。你可以隨時用 `echo $?` 查看上一條指令的退出碼,也可以用 `#` 在註解裡記下「為什麼要非 0」。


git pull
echo "git 退出碼:$?"   # 0 = 成功,128 開頭通常是致命錯誤

場景:三個操作串起來

假設你要完成「備份資料庫 → 跑遷移 → 發通知」,並且**遷移失敗必須終止**,否則會把壞程式碼發出去:


pg_dump mydb > backup.sql && migrate-v2.0 && notify.sh "遷移完成"

`&&` 的好處:`pg_dump` 失敗(磁碟滿、權限錯),後面的就不會跑,你把故障圈在最小半徑。`set -e` 可以讓整個腳本在任何失敗時中止,但它是全域的、容易誤傷(比如 `grep` 搜不到東西也是非 0),精細控制還是靠顯式 `&&` 舒服。

什麼時候該用什麼

- **需要「成功才繼續」:用 `&&`**。建構、遷移、部署,每一步的失敗都會汙染下一步。

- **需要「失敗也別停下來,先記錄」:用 `;`**。

```bash

npm run build; echo "建構結束(成功與否都會列印)"

```

或者更優雅地用 `||` 接失敗分支:

```bash

npm run build || { echo "建構失敗" >&2; exit 1; }

```

- **需要「二選一」:用 `||` 接兜底**。

- `command1 || command2`:command1 成功就跳過,失敗才跑 command2。

- 但上面開頭那個 `A && B || C` 的寫法要慎用。直覺上「成功走 B,失敗走 C」是對的,但有第三種情況:**A 成功、B 失敗**時,C 也會跑。如果你的 B 是 `deploy`,你想用 C 做「部署失敗告警」,這倒是對的;但如果 C 是別的副作用,就很容易出現「部署成功還在執行 C」的詭異 bug。安全寫法:

```bash

if git pull; then

deploy.sh

else

echo "拉程式碼失敗,中止" >&2

exit 1

fi

```

語義一目了然,沒有歧義。

一個真實的坑:`echo` 掩蓋了失敗

很多人寫:


$TOOL_MIGRATE || echo "次要資料遷移出錯"

看起來 `||` 只會在 `$TOOL_MIGRATE` 失敗時輸出告警。但如果你加過 `set -x` 或 debug 日誌,會發現 $TOOL_MIGRATE 真的失敗了,而且告警印出了——沒錯,那是對的;問題是很多團隊的告警通道在這台機器上根本不通(網路隔離、log 服務重啟),`echo` 只是「看起來兜住了」,實際上是**靜默失敗**。`||` 不保證你的 fallback 也成功,它只保證「成功時不跑、失敗時跑」。所以告警要雙通道,且用 `echo ... >&2`,再配合 cron/atop/logwatch 做最終兜底。

管線與 `;` 的結合

管線 `|` 傳遞的是 **stdout**,退出碼預設取**最後一個**指令的退出碼:


cat data.json | jq '.items' > out.json && echo "OK"

如果 `jq` 出錯了(欄位不存在),`echo "OK"` 還是會列印,哪怕 `out.json` 是空的或者報錯。要「任何一段失敗都整段失敗」,需要 pipefail:


set -o pipefail
cat data.json | jq '.items' > out.json && echo "OK"

現在 jq 失敗,管線整體非 0,`&&` 後面的就停了。這是處理資料鏈(日誌採集 → 轉換 → 入庫)最常被忽略的一行。

何時不該用命令鏈

- **超過 3~5 步的流程不要寫成一行鏈**。一行程式碼越長,可讀性越差,且退出碼一多你就追不清了。超過 3 步,寫個 `.sh` 檔案,每步一個函式,函式開頭 `set -euo pipefail`,每步結束打一條 `[step]` 日誌。

- **鏈在前台跑的狀態、錯誤處理、重試**:鏈式指令沒有重試、沒有超時、沒有分段狀態。如果這是生產腳本,至少加 `timeout` 和手動 `for i in 1 2 3; do ... && break; done` 結構。

- **在互動式 shell 裡除錯鏈式指令**:`Ctrl-Z` 會掛起整段鏈,恢復時容易亂序。除錯時拆成單步執行。

常見誤判

1. **「失敗跳過」是 `||`,不是 `;`。** `;` 永遠執行下一條,它沒有「跳過」的概念。

2. **`set -e` 不是萬能的。** 它在全域層面對 `&&`、`||`、`if` 的條件陳述式有豁免(這些是正常控制流,不算錯誤),這在 `make` 的 recipe、Ansible 的 inline task 裡經常踩坑。顯式 `&&`/`||` 永遠優先。

3. **管線退出碼預設只看最後一個指令。** 上面的 `&& jq ... && echo OK` 不會在 jq 失敗時停,除非加 pipefail。

4. **`$?` / `$status` 取上一次指令的退出碼。** 注意是「上一條」,寫了 echo 之後 `$?` 就變成 echo 的(echo 基本恆為 0)。取退出碼要立刻取,別中間插任何指令。

清單:寫鏈前過一遍

- [ ] 這一步失敗,下一步有沒有必要繼續?是 → `;`;否 → `&&`

- [ ] `||` 的兜底分支本身會不會出錯?會 → 用函式封裝,函式內再設兜底

- [ ] 有管線,是否加了 `pipefail`?

- [ ] 關鍵錯誤碼是不是要單獨判斷(別只靠非 0/0 二分)?

- [ ] 超過 3 步?考慮寫腳本,別繼續加 `&&`

- [ ] 在帶 `set -e` / `set -x` 的環境裡跑嗎?是否會被靜默忽略(條件陳述式豁免)?

小結

命令鏈本身很輕,但它的靜默路徑也多。把 `&&` 當「守護門」、`;` 當「無腦接力」、`||` 當「兜底通道」,再用 explicit if/else 處理「二選一邊」、`pipefail` 處理管線,你可以把腳本從「能跑」推到「跑崩時一眼看出崩在哪一步」。

最後一句反直覺但很重要的話:**能寫成 if/else 的分支,就不要用 `||` 的隱含語義。** 下一位接手你腳本的人,包括六個月後的你,會感謝你的。

⚙️ 安装与赋能

clawhub install skill-20260908-command-chains

安装后在你的 Agent 配置中启用此技能,重启 Agent 即可生效。