惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
MyScale Blog
MyScale Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
博客园 - 司徒正美
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
N
News | PayPal Newsroom
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LINUX DO - 热门话题
有赞技术团队
有赞技术团队
V
Visual Studio Blog
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Project Zero
Project Zero
B
Blog RSS Feed
J
Java Code Geeks
Google Online Security Blog
Google Online Security Blog
Last Week in AI
Last Week in AI
Cyberwarzone
Cyberwarzone
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
小众软件
小众软件
博客园 - 【当耐特】
Latest news
Latest news
T
Threat Research - Cisco Blogs
aimingoo的专栏
aimingoo的专栏
博客园_首页
博客园 - 三生石上(FineUI控件)
Engineering at Meta
Engineering at Meta
D
Docker
Forbes - Security
Forbes - Security
Help Net Security
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
P
Proofpoint News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
V2EX - 技术
V2EX - 技术
N
Netflix TechBlog - Medium
The Last Watchdog
The Last Watchdog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Threatpost
Cloudbric
Cloudbric
T
The Exploit Database - CXSecurity.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 叶小钗
Webroot Blog
Webroot Blog

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 读:AI Agent 生产化——一份从原型到上线的速查清单 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
用 Org Babel 写 Literate 博文:扩展执行 + 定制导出
2026-04-25 · via 暗无天日

Literate programming 的核心想法是:把代码和解释它的散文交织在一起,代码可以被实际执行,输出直接嵌入文档。Org mode 的 Babel 就是做这件事的工具——它比 Jupyter Notebook 更灵活(支持任何有 REPL 的语言),但开箱即用时缺少两个东西:REPL 风格的交互式代码块(逐行执行、交替显示输入和输出),以及把 Org 文档导出为静态站生成器能用的 Markdown(带 YAML frontmatter)。这篇文章分两部分解决这两个问题:教你为任意语言写一个自定义 Babel 执行函数,和派生一个自定义 Org 导出后端。

第一部分:为任意语言扩展 Babel

Babel 的执行机制很简单:只要定义一个名为 org-babel-execute:lang 的函数(把 lang 换成你的语言名),Babel 就知道怎么执行该语言的代码块。这个函数接收两个参数——代码体(body)和参数列表(params),返回执行结果的字符串。Babel 负责剩下的所有事情:解析代码块、收集参数、显示结果。

最简执行函数

假设你想为一种叫 mylang 的语言添加 Babel 支持。最简单的实现是调用外部命令执行代码:

(defun org-babel-execute:mylang (body params)
  "Execute a block of MyLang code with org-babel."
  (shell-command-to-string
   (format "mylang -e %s" (shell-quote-argument body))))

定义了这个函数之后,Org 文件里的 #+begin_src mylang 代码块就能用 C-c C-c 执行了。

有一个容易踩的坑:Babel 默认把代码包在一个函数里、调用函数、显示 返回值 而不是输出结果。如果你的代码用了 print 之类的 I/O 操作,默认行为不会显示 print 的内容。要改为显示输出结果,在src block 加上 :results output 参数后。

REPL 风格的代码块

基本的执行函数把整个代码块当做一个单元执行。但写 literate 博文时,你通常想要 REPL 风格的效果:逐行执行代码,每一行下面紧接着显示输出。

实现方式是定义一个新的结果类型 repl 。当代码块加上 :results repl 时,逐行发送代码到 REPL 进程,收集每行的输出,然后把输入和输出交替排列:

(defun org-babel-mylang--execute-repl (body)
  "Execute BODY line-by-line, returning input/output pairs."
  (let ((lines (split-string body "\n" t "[ \t]+")))
    (mapconcat
     (lambda (line)
       (format "   %s\n%s" line (mylang-evaluate-command line)))
     lines
     "\n")))

然后在主执行函数里根据参数进行分支判断:遇到 :results repl 就走逐行执行,否则走整块执行。

(defun org-babel-execute:mylang (body params)
  "Execute a block of MyLang code with org-babel.
When PARAMS includes `:results repl', evaluate each line separately
and return all results interleaved."
  (let ((result-params (cdr (assq :results params))))
    (if (and result-params (string-match-p "\\brepl\\b" result-params))
        (org-babel-mylang--execute-repl body)
      (mylang-evaluate-command body))))

使用时代码块头部写成这样:

#+begin_src mylang :results repl :exports results :wrap SRC mylang
  x = 1 + 2
  x * 4
#+end_src

三个参数的作用: :results repl 触发逐行执行, :exports results 只导出结果不导出源码, :wrap SRC mylang 把结果包在一个 mylang 代码块里——这样导出到 Markdown 后语法高亮仍然生效。执行后得到:

   x = 1 + 2
3
   x * 4
12

批量执行

写完博文后,你可能想一次性执行所有代码块来更新结果。下面这个函数执行文件中所有 mylang 代码块,但跳过已经是结果块的一部分的代码块(避免重复执行):

(defun org-babel-mylang-execute-all ()
  "Execute all MyLang src blocks not part of a #+RESULTS block."
  (interactive)
  (org-babel-map-src-blocks nil
    (when (and (string-equal "mylang"
                             (car (org-babel-get-src-block-info 'no-eval)))
               (not (progn (goto-char beg-block)
                           (forward-line -1)
                           (looking-at-p "#\\+RESULTS:"))))
      (goto-char beg-block)
      (org-babel-execute-src-block))))

org-babel-map-src-blocks 遍历文件中所有代码块, beg-block 是每个块的起始位置。通过向前看一行检查是否在 #+RESULTS: 下面,跳过那些只是结果的块。

第二部分:派生自定义 Org 导出后端

写完 literate 博文后,需要导出为静态站生成器能用的格式。如果你的站点用 Hakyll、Jekyll、Hugo 等工具,你需要带 YAML frontmatter 的 Markdown。默认的 ox-gfm 不支持自定义 frontmatter 字段,也不把脚注转为 GFM 格式。但可以通过派生后端来添加这些功能。

派生后端的基本方法

Org 的导出系统支持从已有后端派生新后端。 ox-gfm 继承自 ox-mdox-md 又继承自 ox-html 。你可以在任何一层上继续派生,只覆盖需要的部分:

(org-export-define-derived-backend 'my-gfm 'gfm
  :options-alist
  '((:tags "TAGS" nil nil split)
    (:last-modified "LAST-MODIFIED" nil nil)
    (:og-description "OG-DESCRIPTION" nil nil))
  :translate-alist
  '((template . my-gfm-template)
    (footnote-reference . my-gfm-footnote-reference)))

两个关键参数:

  • :options-alist 定义新的导出选项。每个元素是 (ALIST-KEY KEYWORD OPTION DEFAULT BEHAVIOR) ,其中 ALIST-KEY 是导出信息 plist 中的键, KEYWORD 是 Org 文件里写的 #+KEYWORD 名, BEHAVIOR 告诉 Org 如何处理多个值—— split 会把空格分隔的字符串拆成列表(适用于 tags)
  • :translate-alist 把导出元素挂钩到自定义翻译函数。 template 控制整个文档的输出(适合插入 frontmatter), footnote-reference 控制脚注引用的格式

生成 YAML Frontmatter

template 翻译函数接收最终转换后的文档内容(contents),是插入 preamble 的标准位置。下面这个函数从导出信息中提取 titledatetags 等字段,组装成 YAML 格式:

(defun my-gfm--build-yaml (info)
  "Build YAML front matter string from INFO plist."
  (when-let*
      ((lines
        (seq-keep
         (lambda (f)
           (when-let*
               ((field (plist-get info f))
                (val (pcase f
                       (:title          #'car)
                       (:date           #'car)
                       (:tags           (lambda (x)
                                          (mapconcat #'identity x " ")))
                       (:last-modified  #'identity)
                       (:og-description #'identity))))
             (format "%s: %s"
                     (string-trim (pp-to-string f) ":" "\n")
                     (funcall val field))))
         '(:title :date :last-modified :tags :og-description))))
    (concat "---\n" (mapconcat #'identity lines "\n") "\n---\n\n")))

(defun my-gfm-template (contents info)
  "Return complete document string after GFM conversion.
CONTENTS is the transcoded contents string.
INFO is a plist holding export options."
  (concat (my-gfm--build-yaml info) contents))

my-gfm--build-yaml 的逻辑是:遍历字段名列表,对每个字段从 info plist 中取值,用 pcase 根据字段类型选择取值函数( :title:datecar:tags 把列表拼成空格分隔的字符串),格式化为 key: value 行,最后用 --- 包裹成 YAML 块。 seq-keep 自动跳过值为 nil 的字段,所以只有 Org 文件中实际写了的字段才会出现在 frontmatter 里。

处理脚注

默认的 ox-gfm 会把 Org 脚注引用( [fn:1] )直接导出为 HTML( <sup><a ...> ),这不是 GFM 格式。需要覆盖 footnote-reference 翻译函数,改为 GFM 的脚注语法 [^n]

(defun my-gfm-footnote-reference (footnote-reference _contents info)
  "Transcode a FOOTNOTE-REFERENCE element into GFM format."
  (format "[^%d]"
          (org-export-get-footnote-number footnote-reference info)))

脚注内容部分,去掉默认的"Footnotes"标题(静态站生成器会自己处理),直接输出定义列表:

(defun my-gfm-footnote-section (info)
  "Format the footnote section without header."
  (and-let* ((fn-alist (org-export-collect-footnote-definitions info)))
    (format "%s\n"
            (mapconcat
             (pcase-lambda (`(,n ,_type ,def))
               (format "[^%d]: %s"
                       n
                       (org-trim (org-export-data def info))))
             fn-alist
             "\n\n"))))

org-export-collect-footnote-definitions 收集所有脚注定义, org-export-data 递归导出脚注内容。每个脚注格式化为 [^n]: content ,放在文档末尾。

合在一起

完整的工作流程是:

  1. 在 Org 文件中写代码块和散文,用 :results repl 获得交互式输出
  2. C-c C-c 执行单个块,或调用批量执行函数更新所有块
  3. 导出为 GFM,自动生成 YAML frontmatter,脚注转为 GFM 格式
  4. 静态站生成器接过 Markdown,继续后续处理

两个扩展各自独立:你可以只用 REPL 风格的 Babel 块而不定制导出,也可以只定制导出后端而不用 REPL 块。但合在一起,就能在 Org mode 的舒适环境中写完整的 literate 博文,一键导出为静态站可用的 Markdown。