Community寫作與編輯github.com

pwquint24/org-babel-shell

在 Org Babel 中使用 Bash、Zsh、Fish、Dash、Sh 等 shell 進行系統自動化、DevOps、遠端執行與腳本撰寫的完整指南。

org-babel-shell 是什麼?

這份技能文件是針對 Org Babel 中 shell 程式碼區塊的完整參考指南,目標讀者是使用 Emacs 進行 DevOps、系統管理與自動化的開發者。它涵蓋了所有主流 Unix 及 POSIX shell(Bash、Zsh、Fish、Dash、Sh 等)的設定與執行。文件詳細說明了三種執行模式:隔離子 shell(預設,無狀態)、持久互動式 session(透過 `:session`,狀態跨區塊保留)以及非同步背景執行(透過 `:async yes`,避免編輯器凍結)。此外,還深入介紹了專為 shell 操作設計的 header 引數,例如 `:stdin`(將 Org 元素內容作為標準輸入傳遞)、`:cmdline`(傳遞命令列引數)與 `:shebang`(產生獨立腳本以獲得乾淨輸出)。對於遠端系統管理,文件展示了如何結合 TRAMP(`:dir` 引數)透過 SSH 管理遠端伺服器或 Docker 容器。最後也包含了常見陷阱(如 session 中出現 shell 提示字串、命令注入風險)的最佳實務,並提供一個完整的生產環境健康檢查 Runbook 範例,展示如何將表格、變數與非同步執行整合到實際工作流程中。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/pwquint24/org-babel-agent-skills/tree/HEAD/skills/org-babel-shell

Installed? Explore more 寫作與編輯 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Org Babel 中的 Shell 與 Bash 程式碼區塊

Org Babel 提供對 Unix 與 POSIX shell 的全面支援,包括:Bash、Zsh、Sh、Fish、Dash、Csh、Ksh 與 Ash。

Shell 區塊將 Org Mode 轉變為一個互動式的 DevOps 筆記本、操作手冊與系統管理主控台,能夠執行本機指令、背景非同步處理,並透過 TRAMP 經由 SSH 進行遠端伺服器管理。


1. 需求與設定

1.1 Emacs 設定

與需要獨立模組的其他 Babel 語言不同,所有 shell 都由同一個 Babel 函式庫(ob-shell)管理。您只需在 org-babel-load-languages 中加入 (shell . t):

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (shell      . t)))

(注意:在 Org 8.2 之前,此項目的名稱為 sh。在所有現代 Org 版本中,請使用 shell。)

1.2 指定 Shell

在區塊語言標籤中指定確切的 shell 二進位檔名稱(#+BEGIN_SRC <shell>):

# Bash:
#+BEGIN_SRC bash
  echo "目前執行於 $BASH_VERSION"
#+END_SRC

# Zsh:
#+BEGIN_SRC zsh
  echo "目前執行於 Zsh $ZSH_VERSION"
#+END_SRC

# POSIX / Dash(更快速、輕量的執行):
#+BEGIN_SRC dash
  echo "目前執行於 dash"
#+END_SRC

# Fish shell:
#+BEGIN_SRC fish
  set greeting "來自 fish 的問候"
  echo $greeting
#+END_SRC

指定的 shell 二進位檔必須存在於系統的 $PATH 環境變數中。


2. 執行模式:隔離、互動式與非同步

2.1 隔離子 Shell(預設,:session none)

每個區塊都在一個全新的暫時子 shell 行程中執行。環境變數和工作目錄不會延續:

#+BEGIN_SRC bash :results output
  echo "PID: $$"
  export WORKSPACE="/tmp/build"
#+END_SRC

#+BEGIN_SRC bash :results output
  # 此處 $WORKSPACE 為空,因為這在一個新的子 shell 行程中執行:
  echo "PID: $$, WORKSPACE: $WORKSPACE"
#+END_SRC

2.2 互動式 Session(:session <name>)

在 Emacs 的 comint 緩衝區(例如 *dev-session*)內執行。變數、函數與工作目錄會跨多個區塊持續存在:

#+BEGIN_SRC bash :session *dev-session* :results output
  export CLUSTER_ID="prod-east-01"
  cd /var/log
#+END_SRC

#+BEGIN_SRC bash :session *dev-session* :results output
  # 使用前一個區塊設定的狀態:
  echo "正在從 $(pwd) 部署至 $CLUSTER_ID"
#+END_SRC

#+RESULTS:
: 正在從 /var/log 部署至 prod-east-01

2.3 非阻塞非同步執行(:async yes)

長時間執行的腳本(備份、容器建置、模型訓練)在同步執行時會凍結 Emacs。加入 :async yes 即可在背景執行,而不鎖定編輯器!

規則:

  1. :async 必須搭配 :session <name>。
  2. 執行期間,Babel 會在 #+RESULTS: 區塊中插入一個暫時的 UUID 記號。
  3. 背景執行完成後,UUID 會自動被命令的標準輸出取代。
#+BEGIN_SRC bash :session *bg-job* :async yes :results output
  echo "開始資料庫重度備份,時間:$(date)..."
  sleep 5
  echo "備份成功完成,時間:$(date)!"
#+END_SRC

#+RESULTS:
: 開始資料庫重度備份,時間:Sun May 18 14:00:00 UTC 2025...
: 備份成功完成,時間:Sun May 18 14:00:05 UTC 2025!

若要在 session 中同步執行,請傳入 :async no。


3. Shell 專用的 Header 引數

Org Babel 提供三個特別針對 shell 操作設計的專屬 header 引數:

3.1 :stdin <element-name>(標準輸入)

將指定的 Org 元素(表格、純文字或其他程式碼區塊的結果)透過管線傳遞給 shell 命令的標準輸入:

# 純文字輸入:
#+NAME: raw_server_payload
{"node": "alpha", "cores": 8, "status": "active"}

# 將 payload 直接導入 jq:
#+BEGIN_SRC bash :stdin raw_server_payload :results output
  jq -r '.node + " has " + (.cores|tostring) + " cores"'
#+END_SRC

#+RESULTS:
: alpha has 8 cores

(注意:使用 :stdin 的區塊會在暫時子 shell 中求值,並忽略 :session。)

3.2 :cmdline <args>(命令列引數)

將位置引數($1、$2、$@)傳遞給腳本主體:

#+BEGIN_SRC bash :cmdline "staging" 8080 :results output
  ENV="$1"
  PORT="$2"
  echo "正在以 $ENV 模式於連接埠 $PORT 啟動伺服器..."
#+END_SRC

#+RESULTS:
: 正在以 staging 模式於連接埠 8080 啟動伺服器...

⚠️ 安全性警告: :cmdline 引數會由宿主 shell 直接展開。請勿傳遞未受信任或未跳脫的字串,以避免命令注入。

3.3 :shebang <directive>(獨立腳本與乾淨輸出)

強制將區塊寫入一個暫時的可執行檔,並透過其 shebang 指令執行:

#+BEGIN_SRC bash :shebang "#!/usr/bin/env bash" :results output
  for i in {1..3}; do
      echo "步驟 $i"
  done
#+END_SRC

為什麼 :shebang 很重要: 在沒有 shebang 的多行 :session 區塊中,各行會逐行發送到互動式 shell,這可能會在結果中產生多餘的提示字串(例如 bash-5.1$ )。指定 :shebang 或在不使用 session 的情況下執行會產生完全乾淨的輸出。


4. 透過 TRAMP 進行遠端伺服器管理(:dir)

您可以透過在 :dir 中指定 TRAMP 路徑,在遠端伺服器、虛擬機器或 Docker 容器上執行命令:

:dir /ssh:<user>@<host>:<remote-path>
:dir /docker:<container-id>:<path>
:dir /sudo::/etc

範例:遠端系統管理工作流程

# 檢查遠端伺服器的磁碟使用量與記憶體:
#+BEGIN_SRC bash :dir /ssh:[email protected]:/tmp :results output
  hostname
  uptime
  df -h / | awk 'NR==2 {print "根磁碟使用率:" $5}'
#+END_SRC

#+RESULTS:
: bastion.example.com
: 14:22:05 up 42 days,  3:18,  1 user,  load average: 0.12, 0.08, 0.05
: 根磁碟使用率:48%

遠端 Session

將 :dir 與 :session 結合使用,可以在多個區塊之間維持活躍的 SSH 連線,而不必在每個步驟重新連線:

#+BEGIN_SRC bash :dir /ssh:[email protected]:/home/admin :session *remote-bastion* :results output
  WORKDIR=$(mktemp -d)
  cd "$WORKDIR"
  pwd
#+END_SRC

5. 結果格式與表格資料擷取

5.1 定位點分隔輸出轉換為 Org 表格(:results output table)

當 shell 命令產生定位點分隔或結構化的表格輸出時,Babel 會將其解析為原生的 Org 表格:

#+BEGIN_SRC bash :results output table
  printf "PID\tUser\tCommand\n"
  printf "101\troot\tsystemd\n"
  printf "405\tpostgres\tpostgres\n"
  printf "892\tnginx\tnginx: worker\n"
#+END_SRC

#+RESULTS:
| PID | User     | Command       |
|-----+----------+---------------|
| 101 | root     | systemd       |
| 405 | postgres | postgres      |
| 892 | nginx    | nginx: worker |

5.2 將輸出導向檔案連結(:results file :file <path>)

#+BEGIN_SRC bash :results file :file reports/sysinfo.txt
  uname -a > reports/sysinfo.txt
  cat /etc/os-release >> reports/sysinfo.txt
#+END_SRC

#+RESULTS:
[[file:reports/sysinfo.txt]]

6. 傳遞變數與 Bash 陣列(:var)

6.1 純量與字串

#+BEGIN_SRC bash :var port=8000 host="localhost" :results output
  echo "正在連線至 $host 的連接埠 $port ..."
#+END_SRC

6.2 Bash 一維陣列

Bash 支援使用列表語法定義一維陣列參數:

#+BEGIN_SRC bash :results output :var servers='("web-01" "web-02" "db-01")'
  echo "伺服器總數:${#servers[@]}"
  for s in "${servers[@]}"; do
      echo "正在 ping $s ..."
  done
#+END_SRC

#+RESULTS:
: 伺服器總數:3
: 正在 ping web-01...
: 正在 ping web-02...
: 正在 ping db-01...

7. 常見陷阱與安全最佳實務

陷阱原因解決方案
Emacs 在執行 SSH 或 ffmpeg 命令時凍結命令嘗試從子 shell 讀取標準輸入明確重新導向標準輸入(</dev/null)或使用 ssh -n。
結果中出現多餘的 bash-5.1$ 提示字串:session 中的互動式逐行回顯加入 :shebang #!/usr/bin/env bash 或在 session 外部求值。
Here-document 中的變數被意外展開使用 <<EOF 允許 shell 過早展開 $VARS使用加引號的定界符 <<"EOF" 以保持文字字面值並防止過早展開。
sh 區塊中的非 POSIX 語法失敗#+BEGIN_SRC sh 對應至 /bin/sh(在 Debian/Ubuntu 上通常是 dash)如果腳本使用 bash 特有語法([[、((、陣列),請改用 #+BEGIN_SRC bash。
透過 :cmdline 發生命令注入Shell 引數被逐字求值謹慎地為引數加上引號,或改用 :stdin 或 :var 傳遞資料。
非同步區塊的輸出沒有出現:async yes 需要一個活躍的 :session使用 :async yes 時,務必提供一個 :session <name>。

8. 完整 DevOps Runbook 範例

#+TITLE: 生產服務健康狀態 Runbook
#+PROPERTY: header-args:bash :session *runbook* :results output

* 步驟 1:健康檢查設定
#+NAME: cluster_nodes
| Hostname        | Role    |
|-----------------+---------|
| prod-app-01.lan | backend |
| prod-app-02.lan | backend |
| prod-db-01.lan  | storage |

* 步驟 2:探測叢集節點
#+BEGIN_SRC bash :var nodes=cluster_nodes :colnames yes :results output table
  printf "Host\tRole\tStatus\n"
  for row in "${nodes[@]}"; do
      # 解析以定位點/空格分隔的行資料:
      host=$(echo "$row" | awk '{print $1}')
      role=$(echo "$row" | awk '{print $2}')
      
      # 快速 ping 探測:
      if ping -c 1 -W 1 "$host" > /dev/null 2>&1; then
          status="ONLINE"
      else
          status="OFFLINE"
      fi
      printf "%s\t%s\t%s\n" "$host" "$role" "$status"
  done
#+END_SRC

#+RESULTS:
| Host            | Role    | Status |
|-----------------+---------+--------|
| prod-app-01.lan | backend | ONLINE |
| prod-app-02.lan | backend | ONLINE |
| prod-db-01.lan  | storage | ONLINE |

* 步驟 3:非同步執行服務維護
#+BEGIN_SRC bash :session *runbook* :async yes
  echo "正在跨節點啟動滾動式快取清除..."
  sleep 4
  echo "快取清除成功完成。"
#+END_SRC

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相關技能