前两周我把一篇改了三个月的论文终稿发给合作者,ta收到后第一句话不是“结果不错”,而是“你这次具体改了哪些地方?”。那一刻我特别理解ta,因为整篇重写过的章节混在一堆微调里,光靠肉眼根本看不出来。后来我固定用TeX Live自带的latexdiff把所有前后版本diff一遍,生成一份红蓝修订版PDF再发出去,合作者扫一眼就能知道“我做了哪些修改”。这篇东西就把我折腾latexdiff的经验完整写出来,从最基础的命令到多文件项目、中文环境、公式表格那些坑,希望能让你少走点弯路。
1. 为什么需要latexdiff:改稿子最痛的那一刻
1.1 手动标红标蓝,真的会瞎
我自己写论文的习惯是:先写一版,然后一版一版迭代。改到第三轮的时候,说实话我自己都记不清上一版里哪句话是新增的、哪段是挪过位置的、哪个结论是从附录提到正文里的。
最笨但也最常见的做法,是手动把旧版复制一份,然后在正文里用红色标新增、用删除线标删除。这活儿看着简单,实际上极其费眼睛,尤其当正文里混着大量LaTeX命令、公式、引用标签时,你很容易漏掉一两处。更别提交给导师之后,导师自己又改了一版,你还得把导师改过的地方再手动整理一遍——我在这个环节崩溃过不止一次。
后来我意识到,这个场景本质上就是“文本差异比较”。既然代码里有git diff,LaTeX文档为什么不能有对应的工具?latexdiff就是干这个的。它能比较两个LaTeX源文件,生成一个新.tex文件,这个新文件里用不同的颜色和字体把新增、删除、替换的内容标记出来,再由你正常编译成PDF。你不需要一个字节一个字节去对着看,也不用担心遗漏。
1.2 latexdiff到底是怎么干活的
我第一次用这个工具前,以为它会直接输出一个带标记的PDF,事实证明我想简单了。latexdiff本质上是一个Perl脚本,它拿到old.tex和new.tex之后,会先解析两者的LaTeX结构,再把文本切分成可比较的词元,逐段做差异比对,最后输出一个mixed.tex文件,也就是diff.tex。
这个diff.tex并没有被编译,它只是把LaTeX的变更用命令包了起来,比如新增内容套在\DIFadd{}里,删除内容套在\DIFdel{}里。你拿到这个文件之后,还要再用LaTeX编译器正常编译一次,才能得到带标记的PDF。
这一步很多人第一次跑会懵:明明是latexdiff,为什么终端里只刷刷刷打了几行字就结束了,PDF呢?原因就在这——它只是“生成差异源文件”的助手,真正的编译还得交给xelatex或pdflatex。理解了这一点,后面很多看起来“失败了”的状况就都能解释通了。
提示:latexdiff不会帮你编译PDF,它只会生成diff.tex。编译diff.tex是你要自己干的活。
2. 环境准备与第一次跑通
2.1 确认你的环境里有没有latexdiff
latexdiff是TeX Live的一部分,如果你装的是完整版TeX Live,正常情况下直接用就行。我当时的系统是macOS,装的是MacTeX,打开终端敲下面这行一看,工具就在那:
latexdiff --version输出里能看到版本号就说明能用。Windows用户如果装的是完整版TeX Live,在命令行里同样能调用;MiKTeX用户一般也能用,只是在首次调用时可能会弹窗询问是否安装相应组件,选安装就行。
但是有一种情况要注意:如果你当初安装TeX Live时是精简安装,或者是用某个发行版里自己裁剪过的套装,latexdiff可能不在。我遇到过一位同事,机器里能编LaTeX,但一敲latexdiff就提示command not found,后来他用下面的命令把包补上就行了:
tlmgr install latexdiff装完之后再跑latexdiff --version确认。补装时如果网络慢,也可以直接用各高校搭建的开源软件镜像站下载完整版TeX Live,镜像站一般会把整个TeX Live的安装包同步得很完整,装完也就一劳永逸了。
2.2 单文件对比:最基础的用法
假设你有一个论文的主文件叫main.tex,投稿之前你另存了一份main_old.tex作为旧版,然后在main.tex上继续改。这时候要生成修改标记,最简洁的写法是:
latexdiff main_old.tex main.tex > diff.tex这里有个顺序问题我一开始就踩过坑:第一个参数是旧文件,第二个参数是新文件。如果你把顺序写反了,生成的diff.tex会把原来的删除内容当成新增内容标出来,整个PDF看起来就像你在把论文改回旧版本一样,不但返工,还会让合作者看得一头雾水。
生成出diff.tex之后,再按你平时的编译方式编译。如果你平时用的是pdflatex:
pdflatex diff.tex如果文档里涉及中文,或者你平时用的是XeLaTeX,就改成:
xelatex diff.tex第一次打开编译好的PDF时,你会看到默认样式:删除的内容被标成红色带删除线,新增的内容被标成蓝色,公式里的细微变化也都能被摘出来。这种直观程度,比你拿着两个PDF并排对比高到不知道哪里去了。
2.3 编译diff.tex之前先看几行
我后来养成了一个习惯,编译diff.tex之前一定会先打开这个文件扫一眼导言区。因为latexdiff生成的diff.tex基本沿用旧文件的导言区,再根据两边文件的差异做一些修补,但这个修补并不总是完美。
比如旧文件导言区里没有加载xcolor宏包,而diff.tex里用了大量\color命令来控制文字颜色,直接编译就会报\color undefined。又比如默认样式里用到了\sout命令删除线,这需要ulem宏包支持,如果源文件没加载过,diff.tex编译时同样会报错。
解决办法很简单,在运行latexdiff之前,先确认两个源文件的导言区里都加上了这几个宏包:
\usepackage{xcolor} \usepackage[normalem]{ulem}如果你不想动源文件,也可以在生成diff.tex之后手动编辑它的导言区补上这两行。我的习惯是前者,因为源文件的导言区本来就该规范,加上这两个包对正文没有任何副作用。
3. 从单文件到真实项目:参数与多文件
3.1 标记样式怎么选
latexdiff默认的标记方式是CFONT,也就是彩色文字加删除线。如果你对标记效果有要求,可以用--type参数切换成别的样式,可选的样式大概有这几种:
| 样式 | 标记效果 | 适合场景 |
|---|---|---|
| CFONT | 删除内容红色删除线,新增内容蓝色 | 默认,最通用 |
| FONTSTRIKE | 删除内容加删除线,新增内容仅换字体 | 打印黑白版也看得清 |
| UNDERLINE | 新增内容下划线,删除内容仍删除线 | 期刊要求明显下划线时 |
| PDFCOLOR | 用背景色块区分增删 | 公式多、需要区块感时 |
| WHOLE | 整行/整段标色,不细分每句话 | 大段重写时更干净 |
我在投稿给不同期刊时经常切换样式。有的期刊编辑要求修改稿必须用下划线标出新内容,有的要求删除内容不能显示太多,怕占版面。这时候就可以用--type=UNDERLINE或--type=FONTSTRIKE来适应。
不过要提醒一句,样式切换后编译产物对宏包的依赖略有不同。比如UNDERLINE样式更依赖ulem,PDFCOLOR样式会用到颜色的背景宏包。如果编译报错,优先去diff.tex导言区补对应的宏包。
3.2 自定义宏和命令安全列表
写LaTeX论文的人基本都会自定义一些命令,比如把期刊名缩写成一个\journalname,或者把某个经常出现的专有名词封成\method{}。latexdiff对于LaTeX内置命令的处理规则比较成熟,但对你自己定义的宏,就没那么聪明了。
举个例子,我在一篇论文里定义了:
\newcommand{\algo}[1]{\textsc{#1}}正文里写\algo{FastFilter}。如果新版里改成了\algo{FastFilterV2},latexdiff可能不知道怎么处理\algo这种未知命令,最坏的情况下会把整个命令标成删除又新增,甚至影响编译。
这时候要用--append-textcmd参数,告诉latexdiff哪些命令应当被当作文本命令来比较:
latexdiff --append-textcmd=\algo main_old.tex main.tex > diff.tex如果命令不止一个,用逗号分隔。反过来,如果你希望某些命令的变化不被标记,比如期刊要求某些内容不能出现在修订标记里,可以用--exclude-textcmd:
latexdiff --exclude-textcmd=\algo,\url main_old.tex main.tex > diff.tex这个“命令安全列表”的思路实际应用时很灵活。我投稿前一般会先在旧版和新版里搜一下自定义命令的数量,然后把它们统一加到--append-textcmd后面,这样diff出来的结果才不容易出现奇怪的整段标记。
3.3 多文件工程:flatten与latexdiff-vc
大部分论文不会把所有内容塞在一个.tex文件里,通常是main.tex加一堆\input{chapter1}、\include{chapter2}。直接用单文件命令对比主文件,diff出来的内容只会看到\input这一行有没有变化,根本看不到子文件里改了啥。
这时候要用--flatten参数。它的作用是先把所有\input、\include的子文件展开合并成一个虚拟的完整文件,再去做差异对比:
latexdiff --flatten main_old.tex main.tex > diff.tex生成diff.tex时你可能会发现文件特别长,因为它已经把各个章节都平铺进去了。后续编译依旧正常,但diff.tex会比较大,编译速度会慢一些。
如果你的项目用git管理,还有另一个更省心的路径:latexdiff-vc。比如:
latexdiff-vc --git -r HEAD~1 main.tex这条命令会自动比较当前工作区与上一次提交之间的差异,并生成带修订标记的PDF。latexdiff-vc还支持svn、hg等版本控制工具,核心参数都差不多。但如果你当前工作区有未提交的修改,某些版本控制场景下它可能不会按你预想的方式运行,所以我个人还是更习惯一个通用方案:先用git导出旧版,再跑latexdiff --flatten:
git show HEAD~1:main.tex > main_old.tex latexdiff --flatten main_old.tex main.tex > diff.tex这样不依赖latexdiff-vc的具体行为,只要git能用就行。
4. 公式、表格、图片:最容易翻车的三个地方
4.1 公式的颗粒度问题
我最初以为latexdiff处理公式是直接把整个公式标记成新增或删除,实际用下来才发现它会深入到公式内部。比如\alpha改成\beta,默认情况下diff会把\alpha标成删除、把\beta标成新增,整页公式看起来密密麻麻全是红蓝小碎片。
这种标记对审稿人来说反而不友好,他们只想知道这个公式是不是大改,并不想看清楚每一个符号的变化。所以我通常在涉及大量公式的文档里加一个参数:
latexdiff --math-markup=0 main_old.tex main.tex > diff.tex--math-markup后面跟的数值是标记颗粒度。数值越大,标记越精细;设为0时,是把整个数学环境当作一个整体来处理,要么整段标新增,要么整段标删除,视觉上干净很多。
如果公式只是小改,你可能还是希望看到符号级差异,那可以把值调成1或2。我个人的建议是:初稿阶段用默认值看细节,定稿前生成给合作者看的那种版本时用0,否则红蓝噪声太大,反而掩盖了真正的结构变化。
4.2 表格改多了会崩
表格是我踩坑最多的地方。latexdiff在文本段落里表现很好,但一进到tabular环境就容易出问题。特别是当你改了列数、增删了&符号、或者调整了列对齐方式时,diff.tex里可能出现对齐符数量不一致的情况,编译直接报Extra alignment tab has been changed to \cr。
后来我的处理策略分几种情况。如果只是改单元格里的某个数值,latexdiff一般能正常标记,很实用;如果整张表格从3列改成4列,那我不再强求latexdiff去做单元格级标记,因为结构变化本身就说明表格做了大改,评审人一眼就能看明白。
遇到需要手动处理的情况,我会先跑一遍latexdiff,然后打开diff.tex定位到表格区域,把列定义部分统一成新版的,再把那些已经错乱的&符号修好。虽然麻烦,但比完全手动标整个表格快得多。
4.3 图片路径变更的干扰
图片这块,最常见的情况是旧版用的是\includegraphics{fig_v1.pdf},新版换成了\includegraphics{fig_v2.pdf}。latexdiff会把路径字符串也当作文本差异标记出来,于是一整行红蓝标记就出来了。
问题在于,如果图片只是生成参数微调后的重绘,你和合作者其实更关心图片本身内容,而不是路径名。这种标记有时候还会干扰编译,因为diff.tex里保留的路径可能指向不存在的文件。
我现在遇到图片变更,基本是两种做法:要么在运行latexdiff前,把新文件拷贝成旧文件名,让路径不发生变化;要么生成diff.tex后,手动把图片相关的那几行红蓝标记清理干净。如果你有一堆图片路径都变了,还可以统一在diff.tex里搜索includegraphics,一键替换成新版路径。
5. 中文文档与编码:XeLaTeX下的避坑记录
5.1 UTF-8和Perl
中文用户最关心的肯定是编码问题。我现在用的TeX Live版本对UTF-8支持已经很好了,直接对包含中文的.tex文件跑latexdiff,基本不会出现乱码。但如果你用的版本比较旧,或者是在Windows上手动改过文件编码,就可能遇到Perl脚本读取文件时把UTF-8中文解析成乱码的情况。
这时候可以强制指定Perl的IO层为UTF-8,命令是这样的:
perl -CSD $(which latexdiff) main_old.tex main.tex > diff.tex-CSD参数的意思是让Perl把标准输入输出和文件句柄都按UTF-8处理,实测下来对老版本环境有很大改善。平时在新版本上,直接调latexdiff就行,不需要套这层perl。
另外要强调一个基础但很重要的习惯:所有源文件统一存成UTF-8编码。有些编辑器默认会存成GBK或GB2312,在Linux和macOS上编译时容易出问题,latexdiff处理时也会跟着遭殃。把所有文件统一成UTF-8,能省掉后面一大堆莫名其妙的乱码问题。
5.2 导言区宏包缺失的连锁反应
中文文档通常会加载ctex宏包或使用ctexart文档类。latexdiff本身不会破坏这些宏包,但它生成的diff.tex会用到一些标记命令,这时候如果源文件的导言区没有兼容好,很容易出现连锁报错。
比如中文模板里一般会自动加载xcolor,但有些精简模板没有。我遇到过一次比较典型的报错:diff.tex编译到一半,提示\color undefined,当时我第一反应是宏包冲突,排查了半天才发现就是旧版导言区缺了xcolor,而新版导言区加了xcolor,latexdiff又把这个差异忽略了,导致diff.tex继承了旧版没有xcolor的导言区。
解决方式前面提过:在两个源文件的导言区都加上xcolor和ulem。中文文档的话,推荐用[normalem]选项加载ulem,因为它默认会重新定义\emph的样式,不关掉这个行为可能会改变你原本的强调效果。
\usepackage[dvipsnames]{xcolor} \usepackage[normalem]{ulem}补上之后再用XeLaTeX编译diff.tex,基本就顺畅了。
6. 投稿前我常用的完整流程与问题速查
6.1 一套稳的workflow
现在我每次准备给合作者发修改稿,流程基本固定下来了,分享给你参考:
第一步,先确认源文件都是UTF-8编码,导言区都有xcolor和ulem。这一步检查完,后面能少碰很多编译报错。
第二步,用git或其他方式保留旧版文件。没有git的话,就直接把当前要改的文件复制一份出来,命名为xxx_old.tex。
第三步,在旧版基础上改新版。这里注意,新版文件里如果用了原来没有的自定义命令,记得把自定义命令的\newcommand也留在导言区,否则diff.tex会因为找不到命令定义而编译失败。
第四步,运行:
latexdiff --flatten main_old.tex main.tex > diff.tex如果正文里公式多,我会再加--math-markup=0。如果有自定义命令,就追加--append-textcmd=...。
第五步,先不急着编译,打开diff.tex看一下导言区和开头几段,确认没有重复宏包、没有缺包、没有明显错乱的表格。然后再用XeLaTeX或pdflatex编译。
第六步,生成PDF后从头到尾翻一遍,重点看那些标记比较密集的地方,确认标记逻辑符合直觉。如果发现某些改动没被标记出来,或者标记了不该标记的内容,回到diff.tex里手动微调,或者重新调整参数。
我一般最后会把diff.pdf和new.pdf一起发给合作者,diff.pdf负责回答“你改了什么”,new.pdf负责给一个干净的最终版,对方看起来会非常省心。
6.2 高频问题速查表
下面这张表是我自己遇到过的典型问题,按照“问题-原因-处理”的方式整理出来,希望能对你有帮助:
| 问题 | 常见原因 | 处理方式 |
|---|---|---|
| latexdiff: command not found | 精简安装或未装latexdiff包 | 用tlmgr install latexdiff补装,或换完整版镜像重新安装 |
| 生成的diff.tex编译报\DIFadd undefined | 导言区缺少标记相关宏包 | 在源文件导言区加xcolor和ulem |
| 出现乱码,中文变成一堆符号 | 文件编码不是UTF-8 | 统一保存为UTF-8再运行,必要时用perl -CSD调用 |
| 表格编译报Extra alignment tab | tabular列结构被diff错乱 | 手动修复diff.tex中表格部分,列定义统一用新版 |
| 公式处红蓝标记过多过碎 | 默认math-markup颗粒度过细 | 用--math-markup=0整段标记 |
| 生成的PDF里图片路径不存在 | 图片路径变化且指向旧文件 | 手动把diff.tex中的图片路径替换为新版路径 |
| diff.tex里重复加载了某个宏包 | --flatten展开时导言区合并异常 | 手动删除重复的\usepackage行 |
表格里的问题基本覆盖了90%的日常使用场景。如果你遇到的是其他报错,建议先打开diff.tex看看出错位置的上下文,很多时候手动改一下比纠结参数更高效。
6.3 一点更进阶的玩法
最后分享一个我后来才学会的技巧:latexdiff生成的标记命令本身是可以用宏重新定义的。也就是说,你完全可以自定义一套属于自己期刊模板的标记样式。
比如默认的\DIFadd会把内容变成蓝色,有的期刊要求新增内容用蓝色加粗,旧内容用灰色删除线。你可以在diff.tex的导言区重新定义:
\renewcommand{\DIFadd}[1]{{\color{blue}\bfseries #1}} \renewcommand{\DIFdel}[1]{{\color{gray}\sout{#1}}}修改之后再编译,整个PDF的标记风格就按你的要求来了。这个办法非常适合应对不同期刊的投稿格式要求,比反复调--type参数更精细。我在给一个要求“新增内容必须加粗标蓝”的期刊改稿时,就是靠这个搞定的。