
1. 为什么我劝你别再手动排版文档了如果你平时写技术笔记、整理会议纪要、维护项目文档或者需要把一份内容同时输出成网页、PDF、Word 三种格式那你大概率经历过这种崩溃在 Word 里调了半小时的行距和标题样式复制到网页编辑器里全乱了用 Markdown 写完的文档发给同事却要求必须是 Word 格式论文投稿要求 PDF但导师批注又得用 Word 回传。这些场景背后其实是同一个问题——内容与格式被绑死了。Pandoc 就是专门解决这个问题的工具。它是一个开源的文档格式转换器支持 Markdown、HTML、LaTeX、Worddocx、PDF、EPUB、reStructuredText、AsciiDoc 等几十种格式之间的互转。你可以把它理解成文档世界的“万能翻译官”你只管用最舒服的方式写内容输出格式交给它来搞定。这篇文章适合谁看如果你是刚接触命令行、想找一个靠谱的文档转换方案的新手前两个层级能让你快速上手如果你已经在用 Pandoc 但每次转换都要翻文档查参数中间层级会帮你把常用场景固化下来如果你需要批量处理、自定义模板、甚至把 Pandoc 嵌入自动化流程后两个层级是我踩过不少坑之后总结的进阶路径。整篇内容按五个层级递进展开你可以按需跳读但我建议至少把前两层看完因为后面的所有技巧都建立在那之上。2. 第一层级搞懂 Pandoc 到底在做什么2.1 它不是“格式刷”而是“中间语言”翻译器很多人第一次用 Pandoc 会有一个误解以为它是像格式刷一样直接把 A 格式“刷”成 B 格式。实际上 Pandoc 的工作方式是先把源文档解析成一种内部的抽象语法树AST然后再把这棵树渲染成目标格式。这个设计带来的直接好处是任意两种它支持的格式之间都可以互转不需要为每一对格式单独写转换器。打个比方这就像翻译如果要把中文翻成法文直接翻可能很别扭但如果先把中文转成一种“意义表示”再从意义表示转成法文中间少了很多歧义。Pandoc 的 AST 就是这个“意义表示”。理解这一点很重要因为它解释了为什么有些格式转换会丢失信息——不是 Pandoc 不行而是目标格式本身不支持源格式的某些特性。比如你把一个带复杂表格的 Markdown 转成纯文本表格结构必然丢失因为纯文本没有表格的概念。2.2 安装这件事别想得太复杂Pandoc 的安装是我见过最省心的之一。它本质上就是一个二进制可执行文件没有复杂的依赖链。各平台的常见做法Windows直接去官网下载.msi安装包双击下一步即可。装完之后打开 PowerShell 或 CMD输入pandoc --version能看到版本号就说明成功了。macOS如果你装了 Homebrew一行命令brew install pandoc搞定。没装 Homebrew 的话下载.pkg安装包也一样。Linux大多数发行版的包管理器里都有比如apt install pandoc或dnf install pandoc。但要注意发行版仓库里的版本可能偏旧如果你需要最新特性建议从官方 release 页面下载二进制包手动放置。注意如果你后续要输出 PDFPandoc 本身不直接生成 PDF它需要调用一个 LaTeX 引擎如 TeX Live 或 MiKTeX或者其他的 PDF 渲染方案。这是新手最容易卡住的地方我在第三层级会专门讲怎么处理。安装完成后建议先跑一个最小验证新建一个test.md写一行# Hello Pandoc然后执行pandoc test.md -o test.html打开生成的 HTML 看看标题有没有正确渲染。这一步能帮你排除 90% 的环境问题。2.3 最核心的三个参数记住就够用了Pandoc 的命令行参数非常多但日常使用中真正高频的其实就三个参数作用典型用法-o指定输出文件pandoc input.md -o output.docx-f指定输入格式pandoc -f markdown input.txt -o out.html-t指定输出格式pandoc input.md -t rst -o out.rst大多数时候 Pandoc 能根据文件扩展名自动推断格式所以-f和-t可以省略。但有两种情况必须手动指定一是输入文件扩展名不标准比如.txt里其实是 Markdown二是你想用某个格式的特定变体比如markdownpipe_tables启用管道表格语法。我个人的习惯是只要不是标准扩展名一律显式写上-f省得后面排查半天。3. 第二层级把常用转换场景跑通3.1 Markdown 转 Word职场最刚需的场景这个场景我敢说占了日常使用的七成以上。命令本身很简单pandoc notes.md -o notes.docx但直接转出来的 Word 往往不尽如人意——标题样式是 Pandoc 默认的字体、行距、页边距都不是你想要的。这时候需要用到--reference-doc参数。它的逻辑是你先用 Word 手动做一个“样式模板”文档把标题 1、标题 2、正文、引用等样式调成你要的样子然后把这个文档作为参考传给 Pandoc。pandoc notes.md --reference-docmy-template.docx -o notes.docxPandoc 会读取这个模板里的样式定义应用到生成的文档上。这个技巧的价值在于你只需要调一次模板之后所有转换出来的 Word 都是统一风格。我见过不少团队把模板文档放在项目仓库里所有人共用出来的文档格式完全一致省掉了大量互相“对齐格式”的时间。实操心得制作参考模板时不要只改标题样式正文的字体、段落间距、甚至页眉页脚都要设置好。另外模板文档里不要留任何正文内容只保留样式定义否则那些内容可能会被带进输出结果。3.2 Markdown 转 PDF绕开 LaTeX 的坑PDF 转换是新手最容易受挫的地方。Pandoc 默认走 LaTeX 路线这意味着你机器上得有一个可用的 LaTeX 环境。完整安装 TeX Live 动辄几个 GB对只想转个文档的人来说太重了。我的建议是分情况处理。如果你只是偶尔转一下、对排版要求不高可以用--pdf-engine指定更轻量的引擎。比如用wkhtmltopdf走 HTML 渲染路线pandoc notes.md -o notes.pdf --pdf-enginewkhtmltopdf这个方案的好处是不需要 LaTeX坏处是对复杂数学公式和精细排版支持有限。如果你经常处理学术文档、需要公式和交叉引用那还是老老实实装一个精简版的 LaTeX 发行版比如 TeX Live 的basic方案只装必要的包。另一个常见需求是控制 PDF 的页面设置比如纸张大小、边距、字体大小。这些通过-V参数传给 LaTeX 模板pandoc notes.md -o notes.pdf -V geometry:margin2.5cm -V fontsize12ptgeometry是 LaTeX 的页面布局包margin控制边距fontsize控制字号。这些参数在 Pandoc 的文档里没有全部列出需要你对 LaTeX 有一点了解。我当初就是被这个卡了很久后来才明白-V传的其实是模板变量具体支持哪些取决于你用的模板。3.3 批量转换一条命令处理整个文件夹单个文件转换用上面的命令就够了但如果你有一个文件夹的 Markdown 要全部转成 HTML一个个敲命令太蠢了。Linux 和 macOS 下可以用 shell 循环for f in *.md; do pandoc $f -o ${f%.md}.html doneWindows PowerShell 下写法不同Get-ChildItem -Filter *.md | ForEach-Object { pandoc $_.Name -o ($_.BaseName .html) }这里有个细节值得说${f%.md}是 shell 的参数扩展语法意思是“去掉变量 f 末尾的 .md”。这个技巧在处理批量文件时非常实用比用sed或basename简洁得多。我第一次看到这个写法的时候还专门查了半天后来发现它是 POSIX 标准的一部分各种 shell 都支持。4. 第三层级用模板和元数据控制输出4.1 元数据块让文档自己说明自己Pandoc 支持在 Markdown 文件开头写一段 YAML 格式的元数据块用三个短横线包裹--- title: 项目周报 author: 张三 date: 2024-06-01 ---这些元数据在转换时会被自动填入模板的对应位置。比如转 HTML 时title会成为title标签的内容转 PDF 时author和date会出现在标题页上。这个机制的价值在于文档的元信息跟着内容走而不是散落在命令行参数里。你换一种输出格式元数据依然有效。元数据块里还可以放自定义字段配合自定义模板使用。比如你定义一个company字段然后在模板里引用它就能实现公司名称的自动填充。这个用法在需要批量生成带统一抬头的文档时特别有用。4.2 自定义模板从“能用”到“好用”的分水岭Pandoc 的模板系统基于一种简单的变量替换语法。你可以用--template指定自定义模板模板里用$variable$的形式引用变量。获取默认模板的方法是pandoc -D html my-template.html这会输出 Pandoc 内置的 HTML 模板你可以在此基础上修改。模板里常见的变量包括$title$、$body$、$toc$目录、$date$等。$body$是特殊变量代表转换后的正文内容必须保留。我拿一个实际场景举例。假设你要把一批 Markdown 转成带统一页头和样式的 HTML 页面默认模板太朴素了。你可以复制默认模板在head里加上自己的 CSS 链接在body开头加上导航栏的 HTML 片段然后保存为my-template.html。之后转换时加上--templatemy-template.html所有输出就都带上了你的自定义样式。注意模板里的变量名是大小写敏感的$title$和$Title$不是一回事。另外如果某个变量在元数据里没有定义模板里对应的位置会留空不会报错。这个特性有时候会导致“为什么我的标题没显示”这类困惑排查时先检查元数据块里有没有写对字段名。4.3 目录与编号长文档的必备配置处理长文档时目录和章节编号是两个高频需求。Pandoc 用--toc生成目录用--number-sections给章节自动编号pandoc thesis.md -o thesis.pdf --toc --number-sections --toc-depth3--toc-depth控制目录包含到几级标题默认是 3。这个参数在写论文或技术手册时很有用因为你不希望四级、五级标题也塞进目录里那样目录会长得没法看。这里有个坑我踩过--number-sections在输出 HTML 时编号是写在标题文本里的但在输出 PDF 时编号是由 LaTeX 模板控制的。这意味着如果你自定义了 LaTeX 模板可能需要额外配置才能让编号正常显示。我的建议是如果编号对你很重要先在默认模板下测试通过再逐步替换成自定义模板这样出问题时容易定位是哪一层的问题。5. 第四层级过滤器与自动化扩展5.1 过滤器是什么为什么需要它Pandoc 的 AST 机制带来了一个强大的扩展能力过滤器filter。过滤器本质上是一个程序它接收 Pandoc 解析出的 AST对树进行修改然后把修改后的树交还给 Pandoc 继续渲染。这相当于在“解析”和“渲染”之间插入了一个自定义处理环节。举个实际例子。Markdown 本身没有“给外部链接自动加图标”的语法但你可以写一个过滤器遍历 AST 里所有的链接节点如果链接指向外部域名就自动在链接文本后面插入一个图标。这样你写文档时只需要写普通链接图标的事情交给过滤器自动完成。Pandoc 官方推荐用 Lua 写过滤器因为 Pandoc 内置了 Lua 解释器不需要额外安装运行时。一个最简单的 Lua 过滤器长这样function Link(el) el.content:insert(pandoc.Str( [外链])) return el end把这个文件保存为add-icon.lua转换时加上--lua-filteradd-icon.lua即可生效。这个例子的逻辑是每遇到一个链接元素就在它的内容末尾追加一个字符串。虽然简单但展示了过滤器的核心工作方式——拿到元素、修改元素、返回元素。5.2 用过滤器解决实际问题自动编号图表技术文档里经常需要给图片和表格编号比如“图 1”“表 2”。手动编号的麻烦在于一旦中间插入或删除一个图后面所有编号都要改。用过滤器可以自动处理。思路是这样的遍历 AST维护一个计数器每遇到一个图片或表格元素就给它的标题前面加上编号。Lua 过滤器里可以用全局变量保存计数器状态local fig_count 0 function Image(el) fig_count fig_count 1 local prefix 图 .. fig_count .. table.insert(el.caption, 1, pandoc.Str(prefix)) return el end这段代码的逻辑是每次遇到 Image 元素计数器加一然后在 caption 的开头插入编号文本。实际使用时还需要考虑图片是否有 caption、caption 的结构是块级还是行内等问题但核心思路就是这样。我当初写这个过滤器的时候光是搞清楚el.caption的数据结构就花了不少时间建议你直接用pandoc -t native把一段带图片的 Markdown 转成原生 AST 格式看看结构一目了然。5.3 把 Pandoc 嵌入自动化流程当你需要定期生成文档时手动敲命令就不合适了。常见的做法是写一个 shell 脚本或 Makefile把转换逻辑固化下来。比如一个生成周报的脚本#!/bin/bash DATE$(date %Y-%m-%d) pandoc weekly.md \ --reference-doctemplate.docx \ --lua-filterauto-number.lua \ -o weekly-$DATE.docx echo 生成完毕weekly-$DATE.docx这个脚本做了三件事获取当前日期、调用 Pandoc 转换、输出结果文件名。把它加到定时任务里每周五自动跑一次你就再也不用记得手动生成了。更进一步你可以把 Pandoc 集成到 CI/CD 流程里。比如每次向文档仓库推送时自动把所有 Markdown 转成 HTML 并部署到静态站点。这个方案在维护开源项目文档或团队知识库时非常实用内容一更新站点自动刷新省掉了手动构建的环节。6. 第五层级性能调优与疑难排查6.1 大文件转换慢怎么办Pandoc 处理普通文档的速度很快但当你转换几百页的文档或者批量处理上千个文件时可能会感觉到明显的延迟。我实测下来影响速度的主要因素有三个第一是 PDF 渲染引擎。LaTeX 引擎启动本身就有开销如果每个文件都单独调用一次累积起来很可观。解决方案是尽量合并转换或者改用更轻量的 HTML 转 PDF 方案。第二是过滤器。Lua 过滤器虽然方便但如果逻辑复杂、遍历次数多会成为瓶颈。我的经验是能在过滤器里用一次遍历解决的问题不要写成多次遍历。另外如果过滤器里有正则匹配尽量预编译正则表达式而不是每次调用都重新编译。第三是图片处理。如果文档里有大量高分辨率图片Pandoc 在生成 PDF 时需要把图片嵌入这个过程比较耗时。一个实用的技巧是在源文档里引用图片时使用相对路径并且提前把图片压缩到合适的分辨率。我一般会把文档用图控制在 150 DPI 左右打印出来足够清晰文件体积也不会太大。6.2 常见报错与排查思路下面这张表是我这些年遇到过的典型问题按出现频率排序报错信息常见原因解决方法pandoc: command not found未安装或未加入 PATH检查安装路径重新配置环境变量Cannot find LaTeX engine缺少 PDF 渲染引擎安装 TeX Live 或改用其他引擎Unknown input format格式名拼写错误用pandoc --list-input-formats查看支持的格式中文乱码字体或编码问题指定-V mainfont或检查文件编码表格错位表格语法不兼容检查是否用了目标格式不支持的表格类型中文乱码这个问题值得单独说。Pandoc 默认的 LaTeX 模板使用的是西文字体直接转中文 PDF 会出现方框或乱码。解决方法是指定一个支持中文的字体pandoc doc.md -o doc.pdf -V mainfontNoto Sans CJK SC前提是你系统里装了这个字体。不同系统上可用的中文字体名称不一样Linux 上常见的是Noto Sans CJK SC或WenQuanYi Micro HeimacOS 上可以用PingFang SC。如果你不确定系统里有哪些字体可以用fc-list :langzh命令列出所有支持中文的字体。6.3 版本升级的注意事项Pandoc 的版本迭代比较快新版本有时会改变某些参数的行为或者调整默认模板的结构。我的建议是生产环境锁定版本测试环境跟进最新版。如果你在脚本或 CI 流程里用了 Pandoc最好在脚本里检查版本号避免因为版本差异导致输出结果不一致。另外Pandoc 的模板格式在 2.x 到 3.x 之间有过一次较大调整如果你有自定义模板升级前务必在测试环境验证一遍。我当初就是从 2.x 升到 3.x 时发现模板里的某些变量名变了导致输出文档的标题页直接空白排查了好一阵才定位到是模板兼容性问题。7. 几个让我少走弯路的实操习惯第一个习惯是始终保留源文件。Pandoc 的转换是单向的从 Markdown 转成 Word 之后你再想从 Word 转回 Markdown格式和结构都会有损失。所以我的做法是所有文档都以 Markdown 为唯一真实来源Word、PDF、HTML 都是“产物”随时可以从源文件重新生成。这样即使输出格式出了问题改源文件重新转一遍就行不用去修产物。第二个习惯是把常用命令写成脚本或别名。我日常用得最多的三条命令分别对应周报、技术笔记和项目文档我把它们写成了 shell 别名敲三个字母就能执行。省下来的时间虽然不多但减少了每次查参数的心理负担让我更愿意用 Pandoc 而不是手动排版。第三个习惯是定期备份参考模板。--reference-doc用的模板文档一旦丢失重新调样式很费时间。我会把模板文件放在版本控制里每次调整都提交一次这样即使改坏了也能回滚。这个做法看起来有点小题大做但当你花了两个小时调好的样式因为一次误操作没了的时候你会感谢自己做了备份。如果你刚开始接触 Pandoc我的建议是从第一层级的安装和基本转换开始先把 Markdown 转 Word 这个场景跑通。等你觉得手动敲命令烦了自然会想去研究模板和脚本。这个过程不用急工具的价值在于用起来顺手而不是把所有功能都学会。