1. 为什么我劝你别再手动调格式了
如果你经常跟文档打交道,一定遇到过这种让人抓狂的场景:用Markdown写完一篇技术笔记,想发给同事看,对方却要Word版本;用Word精心排版的报告,想发布到内部Wiki上,又得重新调整一遍格式;论文投稿要求LaTeX模板,但你手里只有一份Markdown草稿。每次遇到这种情况,大部分人的第一反应是——复制粘贴,然后手动调格式。调着调着,半小时过去了,格式还是乱七八糟。
这就是文档转换工具存在的意义。而在所有文档转换工具里,pandoc是我用了这么多年下来,唯一一个真正称得上“万能”的选手。它能把Markdown转成Word、PDF、HTML、LaTeX、EPUB、PPTX等几十种格式,反过来也行。你可以把它理解成文档世界里的“翻译官”——不管对方说什么语言,它都能帮你翻译过去,而且尽量保留原意。
但问题在于,很多人对pandoc的认知停留在“装完、敲一行命令、转出来能用就行”的阶段。一旦遇到稍微复杂一点的需求,比如自定义样式、批量转换、模板套用,就卡住了。这篇文章就是想把pandoc的使用分成5个层级,从最基础的“能转就行”,一路讲到“把pandoc当成文档流水线的核心引擎”。每个层级我都会说清楚:这个阶段你需要掌握什么、为什么需要掌握、以及实际操作的细节和踩坑经验。
不管你是刚听说pandoc的新手,还是已经用过一段时间但总觉得“差点意思”的中级用户,相信都能在这5个层级里找到自己当前的位置,以及下一步该往哪走。
2. 第一层级:能转就行——pandoc的安装与基础转换
2.1 安装这件事,比你想的简单也比你想的麻烦
pandoc的安装本身不复杂,但不同操作系统下的体验差异挺大。Windows用户直接去官网下载msi安装包,双击下一步就行,装完之后打开PowerShell或者CMD,输入pandoc --version,能看到版本号就说明成功了。macOS用户如果用Homebrew,一行brew install pandoc搞定;不用Homebrew的话,下载pkg安装包也一样。Linux用户最省心,大部分发行版的包管理器里都有,apt install pandoc或者dnf install pandoc就完事了。
但这里有个坑我得提前说:pandoc本身只负责文档格式转换,它不负责生成PDF。也就是说,你想把Markdown转成PDF,pandoc自己做不到,它需要调用一个LaTeX引擎来完成最终的PDF渲染。所以如果你有PDF转换需求,还得额外装一个LaTeX发行版。Windows上推荐MiKTeX,macOS上推荐MacTeX(或者用BasicTeX省空间),Linux上装texlive就行。这个点很多新手不知道,敲了转PDF的命令发现报错,以为是pandoc装坏了,其实只是缺了LaTeX引擎。
提示:如果你暂时没有PDF需求,可以先不装LaTeX,光用pandoc转Word、HTML、EPUB这些格式是完全没问题的。等真正需要PDF的时候再补装也不迟。
2.2 最基础的转换命令长什么样
安装搞定之后,最基础的转换命令其实就一行:
pandoc input.md -o output.docx这行命令的意思是:把input.md转成output.docx。pandoc会根据输入和输出文件的扩展名自动判断格式,你不需要手动指定“我从Markdown转到Word”。这一点设计得很聪明,省去了很多参数记忆的负担。
类似的:
pandoc input.md -o output.html pandoc input.md -o output.epub pandoc input.docx -o output.md基本上就是换个扩展名的事。你可以把pandoc理解成一个“格式路由器”——给它一个输入文件和一个输出文件,它自己会想办法把中间的转换链路走通。
但这里有一个细节值得注意:pandoc默认使用它自己的Markdown方言,叫pandoc markdown。这个方言比标准Markdown多了很多扩展语法,比如脚注、表格标题、定义列表、上标下标等等。如果你写的是标准Markdown(CommonMark),大部分情况下也能正常解析,但遇到一些边界情况可能会有差异。如果你明确知道自己用的是哪种Markdown方言,可以用-f参数指定输入格式,比如-f commonmark或者-f gfm(GitHub风格的Markdown)。输出格式也可以用-t参数指定,不过大多数时候靠扩展名自动判断就够了。
2.3 新手最容易踩的三个坑
第一个坑是中文PDF乱码。这个问题困扰了无数人——Markdown里写的中文,转成PDF之后要么不显示,要么显示成方块。原因很简单:pandoc默认调用的LaTeX引擎使用的是英文字体,不认识中文字符。解决办法有两种:一种是在命令行里指定中文字体,比如-V mainfont="Noto Sans CJK SC";另一种是使用xelatex引擎而不是默认的pdflatex,因为xelatex对Unicode和中文字体的支持更好。完整的命令大概长这样:
pandoc input.md -o output.pdf --pdf-engine=xelatex -V mainfont="Noto Sans CJK SC"第二个坑是图片路径问题。Markdown里引用的图片,如果用的是相对路径,pandoc转换时会相对于当前工作目录去找。如果你在A目录下执行命令,但Markdown文件在B目录,图片又在C目录,那大概率找不到图片。我的习惯是始终在Markdown文件所在的目录下执行pandoc命令,这样相对路径就不会出问题。
第三个坑是转出来的Word样式惨不忍睹。pandoc默认生成的Word文档用的是它内置的参考样式,标题、正文、代码块的字体和间距都是默认值,跟你的审美大概率不搭边。这个问题在第一层级可以先忍一忍,等到第三层级我们再专门解决。
3. 第二层级:批量与自动化——让pandoc替你干活
3.1 什么时候你会需要批量转换
单个文件转换用一行命令就够了,但实际工作中很少只转一个文件。比如你维护了一个文档仓库,里面有几十个Markdown文件,每次发布新版本都要全部转成HTML上传到内部站点。手动一个一个转?那得转到天荒地老。这时候就需要批量转换。
批量转换的核心思路很简单:用脚本遍历目录下的所有Markdown文件,对每个文件执行一次pandoc命令。在Linux和macOS上,用bash写个循环就行:
for f in *.md; do pandoc "$f" -o "${f%.md}.html" done这行脚本的意思是:对于当前目录下所有.md文件,把文件名去掉.md后缀,加上.html后缀作为输出文件名,然后执行转换。${f%.md}是bash的字符串替换语法,表示从变量f的末尾删除.md。
Windows用户如果用PowerShell,写法稍微不同:
Get-ChildItem -Filter *.md | ForEach-Object { pandoc $_.Name -o ($_.BaseName + ".html") }如果你需要递归处理子目录,bash里可以用find命令配合:
find . -name "*.md" -exec sh -c 'pandoc "$1" -o "${1%.md}.html"' _ {} \;这行命令稍微复杂一点,但逻辑是一样的:找到所有.md文件,对每个文件执行转换。-exec后面的sh -c是为了能在命令里使用shell的字符串替换语法。
3.2 用Makefile管理转换流程
脚本虽然能解决问题,但每次改了参数都要去改脚本,时间长了脚本会变得很难维护。更好的做法是用Makefile来管理转换流程。Makefile的好处是:你只需要定义“输入是什么、输出是什么、用什么命令转换”,make会自动判断哪些文件需要重新转换,哪些不需要。
一个简单的Makefile大概长这样:
MD_FILES := $(wildcard *.md) HTML_FILES := $(MD_FILES:.md=.html) all: $(HTML_FILES) %.html: %.md pandoc $< -o $@ --standalone clean: rm -f $(HTML_FILES)这里定义了两个变量:MD_FILES是所有Markdown文件的列表,HTML_FILES是把每个.md替换成.html之后的列表。all目标依赖于所有HTML文件,而%.html: %.md这条规则告诉make:每个HTML文件都从对应的Markdown文件生成,生成命令是pandoc $< -o $@ --standalone。$<表示依赖文件(也就是.md文件),$@表示目标文件(也就是.html文件)。
--standalone这个参数值得说一下。默认情况下,pandoc生成HTML时只输出文档片段,不包含<html>、<head>、<body>这些外层标签。加上--standalone之后,pandoc会生成一个完整的HTML文档,包含头部信息和基本的样式。如果你是要生成一个可以直接在浏览器里打开的网页,这个参数是必须的。
提示:Makefile里的缩进必须用Tab键,不能用空格。这是Makefile的语法要求,也是新手最容易犯的错误之一。如果你用的是VS Code,建议开启“显示空白字符”功能,方便检查缩进。
3.3 自动化触发:文件一变就转换
如果你连“手动执行make”都嫌麻烦,还可以用文件监听工具来实现自动触发。Linux上可以用inotifywait,macOS上可以用fswatch,跨平台的话可以用entr。以entr为例:
ls *.md | entr -c make这行命令的意思是:监听所有.md文件的变化,一旦有变化就执行make。-c参数表示清空屏幕后再执行命令,这样输出更干净。
这套组合拳打下来,你的文档转换流程基本上就自动化了:写完Markdown保存,后台自动转换成目标格式,你只需要关注内容本身就行。这就是第二层级的核心价值——把重复劳动交给工具,把精力留给创作。
4. 第三层级:模板与样式——让输出长得像样
4.1 为什么默认样式总是不尽如人意
到了第三层级,你已经不满足于“能转出来就行”了。你开始在意输出的文档长什么样:标题字体对不对、行间距合不合适、代码块有没有高亮、页边距是不是太窄。pandoc默认的样式是“能用”级别,但离“好看”和“符合规范”还有很大距离。
解决这个问题的核心工具是模板和参考文档。pandoc的模板机制允许你自定义输出文档的结构和样式,而参考文档则让你能够以现有的Word或ODT文档为蓝本,把样式应用到转换结果上。
4.2 用参考文档定制Word样式
Word用户最关心的就是转出来的文档样式。pandoc提供了一个非常聪明的方案:你可以给它一个“参考文档”(reference doc),它会读取这个文档里的样式定义,然后把转换结果套用这些样式。
生成默认参考文档的命令是:
pandoc -o reference.docx --print-default-data-file reference.docx这行命令会把pandoc内置的默认参考文档导出成一个reference.docx文件。你用Word打开这个文件,会看到里面定义了一系列样式:Heading 1、Heading 2、Body Text、Source Code、Block Text等等。你只需要修改这些样式的字体、字号、颜色、间距,保存之后再用pandoc转换时加上--reference-doc=reference.docx参数,生成的Word文档就会套用你修改后的样式。
pandoc input.md -o output.docx --reference-doc=reference.docx这个方案的妙处在于:你不需要学习任何pandoc特有的模板语法,只需要会用Word调样式就行。对于不熟悉编程的文档写作者来说,这是最友好的定制方式。
但这里有几个实操细节值得注意。第一,不要删除参考文档里的任何样式,哪怕你觉得某个样式用不到。pandoc在转换时会根据内容类型去参考文档里找对应的样式,如果找不到,就会回退到默认样式,导致输出不一致。第二,样式名称不要改。pandoc是通过样式名称来匹配的,你把Heading 1改成标题一,pandoc就认不出来了。第三,修改样式时用“修改样式”功能,不要直接选中文字改格式。直接改格式只影响当前选中的文字,不会改变样式定义,pandoc读取的是样式定义而不是具体文字的格式。
4.3 用模板定制HTML和LaTeX输出
Word有参考文档,HTML和LaTeX则有模板。pandoc使用一种叫做“pandoc template”的模板语法,本质上就是带有变量占位符的文本文件。你可以用--template参数指定自定义模板。
导出默认模板的命令是:
pandoc -D html > template.html pandoc -D latex > template.latex导出来的模板文件里有很多$variable$形式的占位符,比如$title$、$body$、$toc$、$date$等等。你可以修改模板的结构,比如在HTML模板里加入自定义的CSS链接、导航栏、页脚;在LaTeX模板里调整页面布局、字体设置、宏包引入。
举个例子,如果你想让生成的HTML自动引入一个自定义CSS文件,可以在模板的<head>部分加上:
<link rel="stylesheet" href="custom.css">然后在转换时用--template=template.html指定模板。这样每次生成的HTML都会自动带上你的自定义样式。
对于LaTeX模板,你可以调整\documentclass、\usepackage、页面边距等设置。比如把默认的article文档类改成ctexart(支持中文的文档类),或者引入geometry宏包来调整页边距:
\usepackage[margin=2.5cm]{geometry}提示:修改模板之前,建议先把默认模板导出备份一份。万一改坏了,还能回退到原始版本。另外,模板里的变量名是大小写敏感的,
$title$和$Title$是不同的变量,改的时候要小心。
4.4 用CSS控制HTML输出的每一个像素
如果你主要输出HTML,那CSS是你最强大的武器。pandoc生成的HTML会带上一些默认的class,比如code、sourceCode、blockquote、table等等。你可以针对这些class写CSS规则,精确控制每一个元素的样式。
比如,你想让代码块有深色背景和圆角:
pre.sourceCode { background-color: #2d2d2d; color: #f0f0f0; border-radius: 6px; padding: 1em; overflow-x: auto; }想让表格有斑马纹:
table tbody tr:nth-child(even) { background-color: #f8f8f8; }这些CSS规则可以写在一个独立的.css文件里,然后通过模板引入,或者直接用-c参数指定:
pandoc input.md -o output.html --standalone -c custom.css-c参数会自动在生成的HTML里插入<link rel="stylesheet" href="custom.css">。配合--standalone使用,效果最好。
5. 第四层级:过滤器与扩展——突破pandoc的默认边界
5.1 什么是pandoc过滤器
到了第四层级,你开始遇到一些pandoc默认功能解决不了的需求。比如:你想在Markdown里写一个自定义的语法,转换时自动变成某种特定的HTML结构;你想在转换过程中自动给所有图片加上图注编号;你想根据文档内容自动生成术语表。这些需求,靠模板和参数已经搞不定了,需要用到过滤器(filter)。
pandoc的过滤器机制非常优雅:pandoc把文档解析成一棵抽象语法树(AST),然后把AST以JSON格式传给过滤器程序,过滤器程序修改AST之后再把结果传回pandoc,pandoc根据修改后的AST生成输出文档。这意味着你可以用任何支持JSON解析的编程语言来写过滤器——Python、JavaScript、Ruby、Haskell,甚至shell脚本都行。
5.2 用Python写一个最简单的过滤器
假设你有一个需求:把所有Markdown里的[待办]标记自动转换成HTML的复选框。用Python写一个过滤器大概长这样:
#!/usr/bin/env python3 import json import sys def convert_todo(text): if text == "[待办]": return {"t": "RawInline", "c": ["html", "<input type='checkbox' disabled>"]} return None def walk(node): if isinstance(node, dict): if node.get("t") == "Str" and node.get("c") == "[待办]": return convert_todo(node["c"]) for key, value in node.items(): node[key] = walk(value) elif isinstance(node, list): return [walk(item) for item in node] return node doc = json.load(sys.stdin) doc = walk(doc) json.dump(doc, sys.stdout)这个脚本的逻辑是:读取pandoc传来的JSON AST,遍历所有节点,找到内容为[待办]的Str节点,把它替换成RawInline节点,内容是一段HTML复选框代码。然后把修改后的AST输出。
使用的时候,把脚本保存为todo-filter.py,加上可执行权限,然后:
pandoc input.md -o output.html --filter=./todo-filter.pypandoc会自动调用这个过滤器,把AST传给它,接收它返回的结果。
这个例子虽然简单,但它展示了过滤器的核心价值:你可以在文档转换的中间环节插入任意自定义逻辑。这个逻辑可以是文本替换、结构重组、内容生成、外部数据查询,只要你能用代码表达出来,就能在转换流程里实现。
5.3 用Lua过滤器做更轻量的定制
Python过滤器虽然强大,但每次都要写一个完整的脚本文件,对于简单的需求来说有点重。pandoc从2.0版本开始内置了Lua过滤器支持,可以直接在命令行里写Lua代码,或者用一个简短的Lua脚本文件。
比如,把所有的二级标题自动加上编号:
function Header(el) if el.level == 2 then el.attributes["number"] = "true" end return el end保存为number-headers.lua,然后:
pandoc input.md -o output.html --lua-filter=number-headers.luaLua过滤器的优势在于:它直接嵌入pandoc的运行时,不需要额外的进程通信,速度更快;而且Lua语法简洁,写小工具非常方便。pandoc官方文档里提供了大量的Lua过滤器示例,覆盖了从简单的文本替换到复杂的文档结构分析等各种场景。
5.4 过滤器的实际应用场景
我在实际工作中用过滤器解决过几类典型问题。第一类是自动化编号:给图片、表格、公式自动编号,并且在正文里自动生成引用链接。第二类是内容注入:根据文档的元数据(比如日期、作者、版本号)自动在页眉页脚插入对应信息。第三类是格式规范化:把文档里不符合规范的写法自动纠正,比如统一日期格式、统一术语拼写。
还有一类比较高级的用法是条件输出。比如同一份Markdown源文件,转成HTML时包含交互式图表,转成PDF时只保留静态图片。这个可以通过过滤器读取输出格式信息来实现:
function Image(el) if FORMAT:match("html") then -- HTML输出时保留原始图片 return el else -- 其他格式时替换成占位文本 return pandoc.Str("[图片]") end endFORMAT是pandoc在Lua过滤器里提供的一个全局变量,表示当前输出格式。你可以根据这个变量来决定过滤器的行为,实现“一份源文件,多种输出形态”的效果。
6. 第五层级:流水线与工程化——把pandoc变成文档基础设施
6.1 从单次转换到持续集成
到了第五层级,你不再把pandoc当成一个“用完就走”的命令行工具,而是把它嵌入到整个文档工程的基础设施里。你的文档仓库可能有几十个文件、多种输出格式、多个目标平台,每次提交代码都会触发自动构建和部署。这时候,pandoc的角色从“转换工具”升级成了“文档流水线的核心引擎”。
实现这个目标的第一步是把转换配置代码化。也就是说,所有pandoc的参数、模板、过滤器、参考文档,都放在版本控制里,跟文档源文件一起管理。任何人克隆仓库之后,只需要一条命令就能完成全部转换,不需要手动配置环境。
一个典型的项目结构大概长这样:
docs/ ├── src/ # Markdown源文件 ├── templates/ # 自定义模板 ├── filters/ # Lua和Python过滤器 ├── styles/ # CSS和参考文档 ├── output/ # 转换输出目录 ├── Makefile # 构建脚本 └── .github/ └── workflows/ └── build.yml # 自动化构建配置Makefile里定义了所有的转换规则,build.yml里定义了什么时候触发构建、构建完成后做什么。每次你往src/目录里推送新的Markdown文件,自动化流程就会启动,执行转换、运行测试、部署到目标位置。
6.2 用元数据块管理文档信息
在流水线化的场景下,文档的元数据管理变得很重要。pandoc支持在Markdown文件开头用YAML元数据块来定义标题、作者、日期、版本等信息:
--- title: "文档标题" author: "某开发者" date: "2024-01-15" version: "1.2.0" tags: [技术, 文档] ---这些元数据可以在模板里通过$title$、$author$等变量引用,也可以在过滤器里读取。更重要的是,你可以用脚本批量读取这些元数据,生成目录页、索引文件、版本对比报告等等。
比如,用一段简单的Python脚本读取所有Markdown文件的元数据,生成一个JSON格式的文档索引:
import os import yaml import json index = [] for root, dirs, files in os.walk("src"): for f in files: if f.endswith(".md"): path = os.path.join(root, f) with open(path, encoding="utf-8") as fh: content = fh.read() if content.startswith("---"): _, meta, _ = content.split("---", 2) data = yaml.safe_load(meta) data["path"] = path index.append(data) with open("output/index.json", "w", encoding="utf-8") as fh: json.dump(index, fh, ensure_ascii=False, indent=2)这个索引文件可以被前端页面读取,用来生成文档导航;也可以被搜索工具读取,用来建立全文检索。
6.3 多格式输出的统一管理
在工程化场景下,同一份源文件往往需要输出多种格式。比如技术文档需要同时输出HTML(用于在线浏览)、PDF(用于打印和归档)、EPUB(用于移动端阅读)。如果每种格式都写一套转换命令,维护起来会很痛苦。
更好的做法是用变量和规则来统一管理。在Makefile里可以这样写:
FORMATS := html pdf epub MD_FILES := $(wildcard src/*.md) TARGETS := $(foreach fmt,$(FORMATS),$(patsubst src/%.md,output/%.$(fmt),$(MD_FILES))) all: $(TARGETS) output/%.html: src/%.md templates/default.html styles/main.css pandoc $< -o $@ --standalone --template=templates/default.html -c styles/main.css output/%.pdf: src/%.md templates/default.latex pandoc $< -o $@ --pdf-engine=xelatex --template=templates/default.latex -V mainfont="Noto Sans CJK SC" output/%.epub: src/%.md styles/epub.css pandoc $< -o $@ --css=styles/epub.css --toc --toc-depth=2这段Makefile定义了三种输出格式的转换规则,每种格式使用不同的模板和参数。$(foreach ...)和$(patsubst ...)是Makefile的文本处理函数,用来根据源文件列表生成目标文件列表。这样你只需要执行make all,所有格式的文档就都生成好了。
6.4 版本控制与差异对比
文档工程化的另一个重要环节是版本控制。Markdown源文件用Git管理是理所当然的,但生成的二进制文件(比如PDF和Word)就不太适合直接放进Git仓库。我的做法是:源文件和配置文件进Git,输出文件放在.gitignore里排除掉,需要发布的时候通过自动化流程生成并上传到制品库。
这样做的好处是仓库体积小、差异清晰。每次提交只包含源文件的变更,Review的时候一眼就能看出改了哪些内容。如果需要对比两个版本的PDF差异,可以在本地生成两份PDF然后用对比工具查看,或者用pandoc把PDF转成文本再对比。
说到差异对比,pandoc本身也提供了一个很实用的功能:你可以把Word文档转成Markdown,然后用Git来管理。这样即使合作方只给你Word文件,你也能把它纳入版本控制体系。命令很简单:
pandoc input.docx -o output.md --extract-media=./media--extract-media参数会把Word文档里的图片提取出来,保存到指定目录,并在Markdown里用相对路径引用。这样转出来的Markdown就是自包含的,图片不会丢失。
6.5 性能优化与大规模文档处理
当文档数量达到几百上千个的时候,转换性能就成了一个需要考虑的问题。pandoc本身的速度已经很快了,单个文件的转换通常在毫秒级别。但如果每个文件都要启动一次pandoc进程,进程启动的开销累积起来也不容忽视。
一个优化思路是合并转换。如果你的文档结构比较统一,可以把多个Markdown文件合并成一个大文件,一次性转换,然后再拆分。pandoc支持用--file-scope参数来正确处理多个输入文件的脚注和引用:
pandoc src/*.md -o output/combined.html --file-scope --standalone另一个思路是并行转换。用xargs或者GNU Parallel来并行执行多个pandoc进程:
find src -name "*.md" | parallel pandoc {} -o output/{/.}.html --standaloneparallel会自动根据CPU核心数分配任务,充分利用多核性能。对于几百个文件的转换任务,并行化通常能把总时间缩短到原来的几分之一。
提示:并行转换时要注意输出文件的命名冲突问题。如果不同子目录下有同名文件,输出时可能会互相覆盖。建议在输出文件名里保留目录结构,或者用哈希值作为文件名的一部分。
7. 五个层级的常见问题与排查技巧
7.1 转换报错怎么快速定位
pandoc的报错信息有时候比较隐晦,尤其是涉及LaTeX的时候。我的排查思路是分三步走:第一步,确认输入文件本身没问题,用pandoc input.md -t native看看pandoc能不能正常解析;第二步,确认输出格式的参数没问题,先用最简参数转换一次,排除模板和过滤器的干扰;第三步,如果涉及PDF,确认LaTeX引擎和字体配置是否正确。
一个很实用的技巧是用--verbose参数查看详细日志:
pandoc input.md -o output.pdf --verbose这个参数会输出pandoc调用的完整命令和中间过程,对于定位问题非常有帮助。
7.2 中文字体和排版问题速查
中文字体问题是pandoc用户遇到最多的问题之一。下面这张表整理了几种常见现象和对应的解决方案:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| PDF中文显示为方块 | LaTeX引擎不支持中文 | 使用--pdf-engine=xelatex |
| PDF中文显示为空白 | 字体未安装或名称错误 | 用fc-list查找正确字体名 |
| Word中文正常但PDF异常 | 参考文档和PDF引擎不一致 | 分别配置参考文档和LaTeX模板 |
| HTML中文乱码 | 编码未指定 | 加-M charset=utf-8或模板里加meta标签 |
| EPUB中文排版差 | CSS未适配中文 | 在CSS里设置line-height和text-align |
7.3 过滤器不生效的排查步骤
过滤器不生效通常有几个原因:过滤器文件没有可执行权限、过滤器脚本有语法错误、过滤器没有正确处理AST节点类型、pandoc版本不兼容。排查的时候可以先用一个最简单的过滤器测试,确认pandoc能正常调用它,然后再逐步增加逻辑。
另外,Lua过滤器和JSON过滤器的调用方式不同:Lua过滤器用--lua-filter,JSON过滤器用--filter。如果混用了,过滤器不会生效,但pandoc也不一定报错,只是静默忽略。这个坑我踩过好几次,后来养成了习惯:每次加过滤器之后,先用一个明显的测试用例验证一下效果。
7.4 批量转换中的路径和编码陷阱
批量转换时最容易出问题的地方是路径和编码。路径问题包括:相对路径基准不一致、文件名包含空格或特殊字符、输出目录不存在。编码问题主要是Windows系统默认使用GBK编码,而Markdown文件通常是UTF-8,导致读取时乱码。
解决路径问题的办法是:在脚本里统一使用绝对路径,或者在执行pandoc之前先cd到正确的目录。解决编码问题的办法是:在Python脚本里显式指定encoding="utf-8",在bash脚本里设置export LANG=en_US.UTF-8或者export LANG=zh_CN.UTF-8。
7.5 版本升级带来的兼容性变化
pandoc的版本迭代比较活跃,新版本有时会引入不兼容的变化。比如某些参数的名称变了、某些默认行为调整了、某些模板变量的含义改了。如果你在自动化流程里使用了固定版本的pandoc,升级之前一定要先在测试环境验证一遍。
我的做法是在项目里用一个pandoc-version.txt文件记录当前使用的pandoc版本,在构建脚本里检查版本号是否匹配。如果不匹配就给出警告,提醒开发者确认兼容性。这个习惯帮我避免了好几次因为版本升级导致的构建失败。
8. 我个人的一些实操体会
用了这么多年pandoc,最大的感受是:它的学习曲线是阶梯式的,但每一级台阶都值得迈上去。第一层级让你摆脱手动调格式的苦海,第二层级让你从重复劳动中解放出来,第三层级让你的输出有了专业感,第四层级让你能解决别人解决不了的问题,第五层级让你把文档工作变成真正的工程实践。
如果让我给新手一个建议,我会说:不要试图一次性掌握所有层级。先用第一层级的命令把日常转换跑通,遇到批量需求了再学第二层级,觉得样式不好看了再研究第三层级。每个层级都是在实际需求的驱动下自然进阶的,硬啃文档反而容易劝退。
另外一个小技巧:pandoc的官方文档虽然很全,但组织方式偏向参考手册,不太适合从头读到尾。我的习惯是遇到问题的时候用pandoc --help查参数,用搜索引擎查具体场景的解决方案,然后在实践中验证。pandoc的社区很活跃,你遇到的问题大概率别人也遇到过,而且已经有现成的答案。
最后再分享一个我常用的命令组合,用来快速检查一个Markdown文件在pandoc眼里的结构:
pandoc input.md -t native | head -50这行命令会把Markdown解析成pandoc的原生AST格式并输出前50行。通过观察AST的结构,你能清楚地看到pandoc是如何理解你的文档的——哪些是标题、哪些是段落、哪些是列表、哪些是代码块。这个技巧在调试过滤器和模板的时候特别有用,强烈推荐你试试。