Community写作与编辑github.com

pwquint24/org-babel-shell

Shell 与 Bash 代码块在 Org Babel 中的完整指南,涵盖系统自动化、DevOps、远程执行及多种 Shell 脚本。

org-babel-shell 是什么?

这份指南详细介绍了如何在 Org Babel 中使用各种 Shell 代码块(Bash、Zsh、Fish、Dash、Sh 等)进行系统自动化、DevOps 操作和远程服务器管理。它支持本地子进程执行、持久会话交互以及异步非阻塞后台任务(`:async yes`),避免阻塞 Emacs。通过 TRAMP 的 `:dir` 参数,可以直接对远程服务器、Docker 容器或 sudo 路径执行命令,实现便捷的运维工作流。该指南还包含了如 `:stdin`(标准输入管道)、`:cmdline`(命令行参数传递)、`:shebang`(独立脚本执行)等专用参数,并提供了表格输出、变量传递(含 Bash 数组)和常见陷阱(如 Emacs 挂起、提示符残留)的解决方案,是 Emacs 用户进行交互式系统管理和自动化脚本编写的必备参考。

兼容平台~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 "Running in $BASH_VERSION"
#+END_SRC

# Zsh:
#+BEGIN_SRC zsh
  echo "Running in Zsh $ZSH_VERSION"
#+END_SRC

# POSIX / Dash(更快、更轻量级执行):
#+BEGIN_SRC dash
  echo "Running in dash"
#+END_SRC

# Fish shell:
#+BEGIN_SRC fish
  set greeting "Hello from 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 <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 "Deploying to $CLUSTER_ID from $(pwd)"
#+END_SRC

#+RESULTS:
: Deploying to prod-east-01 from /var/log

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 "Starting heavy database backup at $(date)..."
  sleep 5
  echo "Backup finished successfully at $(date)!"
#+END_SRC

#+RESULTS:
: Starting heavy database backup at Sun May 18 14:00:00 UTC 2025...
: Backup finished successfully at Sun May 18 14:00:05 UTC 2025!

要在会话中同步运行,请传递 :async no。


3. Shell 专用头部参数

Org Babel 为 Shell 操作提供了三个独有的头部参数:

3.1 :stdin <element-name>(标准输入)

将命名的 Org 元素(表格、原始文本或其他代码块的结果)通过管道传递给 Shell 命令的标准输入:

# 原始文本输入:
#+NAME: raw_server_payload
{"node": "alpha", "cores": 8, "status": "active"}

# 将载荷直接通过管道传递给 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 "Starting server on port $PORT in $ENV mode..."
#+END_SRC

#+RESULTS:
: Starting server on port 8080 in staging mode...

⚠️ 安全警告: :cmdline 参数会由宿主 Shell 直接展开。不要传递未信任或未转义的字符串,以免导致命令注入。

3.3 :shebang <directive>(独立脚本与干净输出)

强制将代码块写入一个临时可执行文件,并通过其 shebang 指令运行:

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

为什么 :shebang 至关重要: 在未指定 shebang 的多行 :session 代码块中,各行会被逐行发送到交互式 Shell,这可能导致结果中出现杂乱的提示字符串(如 bash-5.1$ )。指定 :shebang 或不使用会话模式运行可产生完全干净的输出。


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 "Root Disk Usage: " $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
: Root Disk Usage: 48%

远程会话

将 :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 "Connecting to $host on port $port..."
#+END_SRC

6.2 Bash 一维数组

Bash 支持使用列表语法定义的数组参数:

#+BEGIN_SRC bash :results output :var servers='("web-01" "web-02" "db-01")'
  echo "Total servers: ${#servers[@]}"
  for s in "${servers[@]}"; do
      echo "Pinging $s..."
  done
#+END_SRC

#+RESULTS:
: Total servers: 3
: Pinging web-01...
: Pinging web-02...
: Pinging db-01...

7. 常见陷阱与安全最佳实践

陷阱原因解决方案
Emacs 在 SSH 或 ffmpeg 命令上挂起命令尝试从子 Shell 读取标准输入显式重定向标准输入(</dev/null)或使用 ssh -n。
结果中出现杂乱的 bash-5.1$ 提示字符在 :session 中逐行交互式回显添加 :shebang #!/usr/bin/env bash 或在会话外求值。
heredoc 中的意外变量展开使用 <<EOF 允许 Shell 过早展开 $VARS使用带引号的分隔符 <<"EOF" 保持文本字面量,防止提前展开。
非 POSIX 语法在 sh 代码块中失败#+BEGIN_SRC sh 映射到 /bin/sh(在 Debian/Ubuntu 上通常是 dash)如果你的脚本使用 bashism([[、((、数组),请使用 #+BEGIN_SRC bash。
通过 :cmdline 进行命令注入Shell 参数被逐字求值仔细引用参数,或改通过 :stdin 或 :var 传递数据。
异步代码块输出不显示:async yes 需要一个活动的 :session使用 :async yes 时始终提供 :session <name>。

8. 完整的 DevOps 运行手册示例

#+TITLE: 生产服务健康检查运行手册
#+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 "Initiating rolling cache purge across nodes..."
  sleep 4
  echo "Cache purge completed successfully."
#+END_SRC

Individual skills in this repo

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

相关技能