为什么个人站长也需要一套文档转换流水线
做个人站的人往往要跟各种文档格式打交道:Markdown 写的草稿、Word 发来的投稿、要给访客下载的 PDF 版教程、要贴进 CMS 的 HTML 片段。手工复制粘贴不但慢,样式还会在格式之间来回丢。Pandoc 是一款命令行文档转换器,它把 Markdown、HTML、docx、LaTeX、EPUB、PDF 等几十种格式在内部统一成抽象语法树(AST)再输出,因此它是少数能做到「一次配置、批量复现」的转换工具。把它接进 crontab 或 systemd timer,你就拥有了一条自动化的文档流水线,投稿一进目录,HTML 与 PDF 就自动生成。
本文不讲泛泛的语法大全,而是按个人站长的真实场景来组织:先装好 Pandoc 与 PDF 引擎,再解决中文字体这个最大的坑,然后写出可复用的转换脚本,最后用 systemd timer 让它定时跑。文中的所有命令我都在一台 Debian 12 的 VPS 上实测过。
安装 Pandoc 与 PDF 引擎:别只装一半
Pandoc 自己只负责把源格式转成目标格式的文本,它生成 PDF 的方式是「先转成 LaTeX,再调用外部 LaTeX 引擎排版」。所以只装 Pandoc 是转不出 PDF 的,必须一起装一个引擎。常见的选择有两个:TeX Live 完整版(体积几个 G,但功能全)和更轻量的 xelatex 方案。对个人站来说,直接装 texlive-xetex 加中文字体就够用。
Debian/Ubuntu 上的安装命令:
apt update
apt install -y pandoc texlive-xetex texlive-lang-chinese fonts-noto-cjkCentOS / Rocky 上包名略有不同:
yum install -y pandoc texlive-xetex texlive-collection-langchinese装完后验证版本与引擎:
pandoc --version | head -1
which xelatex如果你的服务器内存很小,另一个省事的思路是放弃本机排 PDF,只让 Pandoc 输出 HTML 与 docx,PDF 交给浏览器打印或客户端处理。流水线的边界要根据自己的资源来划。
中文字体的坑:为什么你的 PDF 全是方块
这是中文用户最容易翻车的地方。Pandoc 走 LaTeX 路线生成 PDF 时,默认使用 Latin Modern 之类不含中文字形的字体,结果整份 PDF 的中文全变成空白或方块。解决方式是显式指定一个中文字体,并且确保这个字体在系统里真的装了。
先确认系统字体里有哪些中文可用:
fc-list :lang=zh | head看到类似 Noto Sans CJK SC 的条目就对了。然后写一个最小的 LaTeX 头文件 header.tex,把主字体设成它:
\usepackage{xeCJK}
\setCJKmainfont{Noto Sans CJK SC}
\setCJKmonofont{Noto Sans Mono CJK SC}转换时用 --pdf-engine=xelatex -H header.tex 把这段头插进去。注意一定要用 xelatex 而不是 pdflatex,只有 xelatex 才支持直接调用系统里的 OpenType 中文字体。这一条如果记不住,中文 PDF 就会一直是方块。
建立目录约定与转换脚本
流水线能不能长期跑下去,取决于目录约定清不清楚。我用的结构是这样:
/srv/docs
├── src/ # 源文件,投稿丢这里
├── html/ # 生成的 HTML
├── pdf/ # 生成的 PDF
└── assets/ # 图片、CSS 等资源源文件一律用 Markdown,文件名用英文或拼音,避免中文文件名在脚本里引号转义出错。接着写一个只转换「有变化」的脚本,用 mtime 判断,避免每次全量重跑。
#!/bin/bash
set -euo pipefail
SRC=/srv/docs/src
OUT_H=/srv/docs/html
OUT_P=/srv/docs/pdf
HDR=/srv/docs/header.tex
mkdir -p "$OUT_H" "$OUT_P"
for f in "$SRC"/*.md; do
base=$(basename "$f" .md)
tgt="$OUT_H/$base.html"
if [ -f "$tgt" ] && [ "$tgt" -nt "$f" ]; then
continue
fi
echo "converting $base ..."
pandoc "$f" \
--standalone --toc --toc-depth=2 \
--metadata title="$base" \
-o "$tgt"
pandoc "$f" \
--pdf-engine=xelatex -H "$HDR" \
--toc --toc-depth=2 \
-o "$OUT_P/$base.pdf"
done
echo "done."脚本里 -nt 判断目标文件是否比源文件新,新则跳过,这就是「增量」的全部秘密。配合 set -euo pipefail,任何一个文件转换失败都会让整轮退出,方便你在告警里发现。
HTML 输出:直接可用的站内片段
个人站更常需要的是 HTML 片段而不是整份 PDF。--standalone 会生成带 <html> 外壳的完整文档,而如果你只想拿正文片段塞进 CMS,就去掉 --standalone,Pandoc 只输出从 <h1> 开始的正文结构。想要更紧凑的语义标记,加上 --no-highlight 关闭代码高亮的内联样式,或 --highlight-style=tango 指定配色。
如果源文里有脚注,HTML 输出会自动生成脚注区的双向锚点,这一点比手工排版省心得多。要生成带目录的整篇页面,用 --toc;要生成只到二级标题的目录,加 --toc-depth=2,避免目录太长。
批量处理 Word 投稿与反向转换
投稿常常是 docx。Pandoc 读 docx 时会尽量保留标题层级、列表、表格与图片。把 docx 转成 Markdown 的命令最短:
pandoc incoming.docx -t gfm --extract-media=./assets -o incoming.md--extract-media 会把文档里内嵌的图片抽到指定目录,避免图片被丢弃。-t gfm 指 GitHub 风格 Markdown,兼容性最好。转完记得检查一下表格,复杂合并单元格在转换后往往需要手工修一下。
反过来,Markdown 转 docx 也常用:
pandoc post.md -o post.docx --reference-doc=template.docx--reference-doc 用一个现成的 docx 当样式模板,这样产出的文档字体、页边距都符合你的品牌风格,不用每次手工调。
用 systemd timer 定时跑流水线
脚本写好之后,交给 systemd timer 定时执行最稳。先写 service 单元:
[Unit]
Description=Pandoc document pipeline
[Service]
Type=oneshot
User=docs
WorkingDirectory=/srv/docs
ExecStart=/srv/docs/convert.sh保存为 /etc/systemd/system/docs-convert.service。再写定时器:
[Unit]
Description=Run pandoc pipeline every 10 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target保存为 /etc/systemd/system/docs-convert.timer,然后:
systemctl daemon-reload
systemctl enable --now docs-convert.timer
systemctl list-timers | grep docs-convert用 systemd timer 而不是 crontab 的好处是:日志自动进 journald(journalctl -u docs-convert 直接看),失败状态可被监控系统捕获,而且支持 OnBootSec 补跑。
模板与样式定制:让输出贴合站点
Pandoc 默认的输出样式很朴素,直接用到站点里往往显得单薄。好在它支持自定义模板,你可以把站点现有的 HTML 骨架抽出来当模板,让转换直接产出符合全站风格的页面。做法是先让 Pandoc 导出一份默认模板:
pandoc -D html > my-template.html导出的模板里有一组变量占位符,比如 $title$、$body$、$toc$、$date$,你把它们的周围改写成自己站点的 <head>、导航、页脚,再在转换时用 --template=my-template.html 引用即可。这样每篇稿子转出来都自动带上统一的导航与样式,省去逐篇贴模板的重复劳动。
除了模板,还可以用 --css 给 HTML 输出挂上外部样式表,配合 --self-contained 把 CSS 和内嵌图片打包进单个文件。若你想给访客一个可直接下载、双击就能看的单文件 HTML,这一组参数非常好用。样式和内容分离之后,改一次 CSS,全站历史文档重新跑一遍流水线就能统一更新,这也是「流水线」相对「手工排版」最有价值的地方。
元数据与 Front Matter 的用法
在 Markdown 文件顶部用 YAML front matter 写元数据,Pandoc 会自动读取并填进模板变量。一个典型写法是这样:
---
title: Nginx 调优实战
author: 老张
date: 2026-10-09
tags: [nginx, 性能]
---有了它,转换时不需要再手工传 --metadata title=...,标题、作者、日期都会自动就位。日期变量还能用 $date$ 直接渲染到页面上。对个人站来说,把作者、分类、标签这些信息写进 front matter,等于把「元数据」和「正文」分开管理,后续想批量按标签生成归档页,只要解析 front matter 就够了,不用再去正文里找线索。
需要提醒的是,YAML 里如果出现冒号、井号等特殊字符,记得用引号包起来,否则解析会出错。中文标题一般没问题,但如果标题里带英文冒号,就写成带引号的形式,避免解析歧义。
常见报错与排查思路
除了字体和内存,Pandoc 还有几个高频报错值得预先知道。若提示找不到模板,检查 --template 路径是不是相对当前工作目录,在 systemd 里跑时工作目录可能和你想的不一样,稳妥起见写绝对路径。若提示 pandoc: Cannot decode byte,通常是源文件不是 UTF-8 编码,老编辑器保存的 GBK 文件要先转码。若 PDF 里表格溢出页面边界,是 LaTeX 的表格列宽问题,可以给 pandoc 加 --pdf-engine-opt 调页边距,或干脆把宽表改成图片。把这些错误和对应解法记成一个清单,下一次别人投稿来各种奇怪文件时,你就不用手忙脚乱了。
资源限制与失败排查
LaTeX 排版很吃内存和 CPU,在低配 VPS 上一旦并发转换多个大文档就可能触发 OOM。用 systemd 给 service 加内存上限,超了直接失败重启,比拖垮整个系统好:
MemoryMax=512M
CPUQuota=80%排查失败时按顺序看:一是 journalctl -u docs-convert -n 50 看 pandoc 报错;二是确认字体是否存在(fc-list :lang=zh);三是如果报 template 相关错误,检查是不是 --standalone 缺了导致模板变量找不到;四是 PDF 输出为 0 字节通常意味着 xelatex 调用失败,单跑一次命令看 stderr。把这几步固定成清单,以后出问题不用重新摸索。
小结
Pandoc 对个人站长的价值不在于「又能转格式了」,而在于把零散的文档处理变成一条可复现、可监控、可增量的流水线。装齐引擎、解决中文字体、写增量脚本、交给 systemd timer,四步下来,投稿进目录就能自动变成 HTML 与 PDF。整套方案不依赖任何在线服务,完全跑在你自己的服务器上,数据和样式都握在自己手里。