写论文改到第N版最痛苦的不是改而是改完之后还得告诉别人到底改了什么。尤其当文档用了LaTeX之后普通文本比较工具基本帮不上忙。我第一次用latexdiff时感觉像找到了救星它能自动把两个LaTeX文件之间的增删改妥妥地标出来生成一份可以直接编译的diff文档谁是新增、谁是删除、哪句话被动过一眼就能看明白。这篇东西就围绕latexdiff的使用全过程展开从安装、跑通、调输出样式到接进Git工作流和踩坑经验全是我在实际项目中验证过的方法适合写毕业论文、投稿返修、多人共同维护一份LaTeX文档的朋友。1. 为什么论文修改记录要用latexdiff而不是肉眼对比1.1 你需要处理的真实场景大部分人的“版本管理”其实是这样今天存一个main_20250101.tex明天再存一个main_20250102.tex改了几句话、换了两个公式、调整了一段表格然后就再也没有人确切记得哪一版是“最终版”。等导师、合作者问起来“你这次改了哪些内容”时你就得手动打开两个PDF并排比对。眼睛扫过一整页找出一两行不同效率极低。这还不算最麻烦的——LaTeX文档里往往有一堆\newcommand、\ref、\input等命令同一个句子只要改了引用的标签肉眼看起来内容是没变的可底层文本已经完全不同。普通diff工具会把一行LaTeX中断成好几段输出结果惨不忍睹。latexdiff要解决的问题很简单让两个LaTeX源文件的差异变得可读、可编译、可分享。它不只告诉你哪一行变了而是按照LaTeX的语法结构去理解文档把“新增内容”“删除内容”“替换内容”分别用标记包起来再输出成一个新的.tex文件。你用正常的编译流程处理这个新文件就能得到一个带红绿标注的PDF谁都能看懂。1.2 普通diff和latexdiff的本质差别很多人一开始不理解明明是文本文件直接用diff命令不就行了吗区别在于“行”和“语义”这两个层级完全不同。普通diff按行比较适合程序源码、配置文件的版本追溯但LaTeX的排版逻辑不是按行组织的。一个句子可以跨行写一个\begin{equation}环境从第3行开始到第30行结束中间任何一行改动都会引发大面积diff误报。比较维度普通difflatexdiff比较单位逐行逐词、逐句、按环境结构对LaTeX命令的处理当普通文本拆开识别命令边界和参数输出结果纯文本片段可编译的LaTeX文件能否生成直观PDF不能能且自动上色标注处理多文件文档不支持支持通过--flatten展开子文件这个差异在实际使用里特别明显。我用普通diff比较一份带表格的论文时\和这类符号被拆得到处都是几乎无法判断真正的改动在哪里。换成latexdiff之后所有差异都落到具体的词或段落上引用、公式、环境这些结构都不会被误伤。1.3 它解决什么不解决什么讲清楚边界后面用起来才不慌。latexdiff能解决的问题包括修改了小段落、增删了句子、替换了关键词、重排了章节顺序、调整了公式内的细节这些都能在输出里标出来。它的输出文件可以直接编译也可以发给没装latexdiff的同事——对方只需要把它当成普通.tex编译即可。但有些事它不负责。第一它不会检查文档能否编译如果新旧版本本身有语法错误diff结果大概率也编译不过第二它不比较参考文献数据库.bib文件的内容只能标出\cite命令引用条目的变化第三它不理解每个人自定义宏的语义如果旧版本里有一个\foo新版本里被换成了\bar它会标出所有相关位置的替换但不保证替换后的命令仍然合法。我习惯把它定位成“差异可视化工具”而不是“LaTeX语义裁判”。真正决定哪些改动是合理的还得靠自己审。2. 装好latexdiff的几道坎从依赖到命令不可用2.1 不同发行版的安装路线latexdiff本身是一组Perl脚本不是必须编译的二进制程序所以安装方式跟着你的LaTeX发行版走。如果你用的是完整版TeX Live一般已经自带latexdiff可以直接在终端敲latexdiff -v验证。如果发现没有这个命令通常是发行版安装时只装了基础方案。Linux下用系统包管理器装是最省事的sudo apt install texlive-extra-utils # Debian/Ubuntu sudo dnf install texlive-latexdiff # Fedora sudo pacman -S texlive-extra-utils # Arch LinuxmacOS用户如果装了MacTeXlatexdiff也在其中如果用的是BasicTeX需要手动补上相应包。Windows用户一般装MiKTeX在MiKTeX Console的包管理界面搜latexdiff就能直接安装。我见过最隐蔽的情况是命令能找到但一运行就报Perl模块缺失。latexdiff依赖不少Perl库某些精简版发行包装完脚本但不装依赖需要用cpan把缺失模块补上。遇到报错时先看缺少哪个module再针对性处理。2.2 命令行不可用的排查顺序如果latexdiff命令第一次运行就报“command not found”我建议按下面顺序排查确认发行版包含latexdiffkpsewhich latexdiff.pl能输出路径说明文件在。检查PATH环境变量TeX发行版的bin目录是否在PATH里。Linux常见路径是/usr/binmacOS用户可能需要eval $(/usr/libexec/path_helper)。运行perl -v确认Perl存在。latexdiff是Perl脚本没有Perl肯定跑不了。执行latexdiff --help如果能打印帮助说明脚本本身没问题。很多人卡在第二步。尤其Windows用户装了多个发行版PATH里指向旧版TeX目录导致新版带的latexdiff根本不会被找到。我自己调试过一台机器装了好几版TeX环境最后发现shell里生效的latexdiff来自一个五年前的精简包更新PATH之后才正常。2.3 --flatten依赖的latexpandlatexdiff有一个非常常用的参数--flatten它能把\input、\include的子文件内容直接展开到主文件中这样diff结果只有一个文件处理起来特别干净。但--flatten不是latexdiff单独完成的它要调用一个叫latexpand的辅助脚本。如果系统里只有latexdiff而没有latexpand运行时会报错。Debian系安装texlive-extra-utils时一般会一起装上但某些自定义安装可能会漏掉。验证方法也很简单latexpand --version没有这个命令的话先解决它再回来用latexdiff。我习惯在所有环境里都先跑一遍latexdiff --version和latexpand --version两个都能出现后面的流程才会顺。3. 第一次完整跑通old.tex与新.tex之间的差异标注流程3.1 最简命令和编译流程假设你手上有两个文件old.tex是上一版new.tex是改后版本最基础的用法就一条命令latexdiff old.tex new.tex diff.tex执行完后当前目录会多出一个diff.tex。这个文件就是融合了旧版和新版所有内容的“标注版”你需要像编译普通LaTeX文档一样编译它pdflatex diff.tex pdflatex diff.tex如果文档里有BibTeX还需要额外跑一轮bibtex diff。为什么要编译两遍因为diff.tex里不仅有你的原文内容还包含一层差异标记命令这些命令需要第一遍编译才能正确写入辅助文件第二遍才稳定显示。我第一次用的时候只顾着生成diff.tex直接用旧版的编译脚本去跑结果PDF里所有标记颜色都消失了只看到文字还以为命令写错了。后来才意识到diff.tex的编译流程和原文档不完全一样尤其是涉及\label、\ref、目录时多编译一遍能解决很多莫名其妙的问题。3.2 diff.tex内部发生了什么把生成的diff.tex用文本编辑器打开你会发现它的结构很有特点导言区多出一整段“差异控制代码”正文里的改动位置被包进了类似\DIFadd{...}和\DIFdel{...}的宏里。举个例子如果old.tex里写的是This is an old statement.new.tex里改成This is a new statement.那么diff.tex里大概会变成\DIFdelbegin \DIFdel{This is an old statement.}\DIFdelend \DIFaddbegin \DIFadd{This is a new statement.}\DIFaddend\DIFadd表示新增\DIFdel表示删除。默认样式里新增内容用蓝色加下划线删除内容用红色加删除线。这套宏定义都在导言区所以diff.tex才能独立编译。理解这个结构的主要意义在于当你对默认标记不满或出现编译冲突时可以直接改动这些宏而不是去改动正文里的差异标记。后面我会专门写一节怎么调样式。3.3 多文件文档必须用--flatten我的论文是主文件加多个\input子文件的结构直接运行latexdiff main_old.tex main_new.tex diff.tex只会比较主文件里直接写的内容子文件里的改动全部看不到。后来才体会到--flatten的重要性latexdiff --flatten main_old.tex main_new.tex diff.tex这个参数会把所有被\input或\include引入的文件内容展开到主文件中再统一做差异比较。这样即使改动发生在第二章的子文件里也能被正确标出来。不过要注意--flatten生成的diff.tex会很大因为它把子文件内容全部塞进来了。如果你只想比较某一个子文件可以直接用latexdiff对那个子文件单独操作没必要每次都展开整个论文。3.4 一个小型示例实操我常用下面这样一组微型文件来测试latexdiff环境是否正常。old.tex\documentclass{article} \begin{document} Hello world. This is version one. \end{document}new.tex\documentclass{article} \begin{document} Hello LaTeX world. This is version two. \end{document}然后执行latexdiff old.tex new.tex diff.tex pdflatex diff.tex生成的PDF里“LaTeX”和“version two”会用新增样式标出来而“version one”会被标成删除。这一套流程能跑通说明环境没问题后面处理复杂文档就不会再被工具本身绊住。4. 标注样式与输出类型让diff结果符合投稿/合作要求4.1 默认样式和两种定制思路latexdiff默认是“删除线下划线颜色”的混合方案适用于大部分内部协作场景。可如果要把diff结果发给导师或期刊编辑有时需要更含蓄的标注方式比如只变色、不画删除线。最简单的改动方式是直接编辑diff.tex里导言区对\DIFadd和\DIFdel的宏定义。我的一般做法是先编译一次diff.tex然后在生成的.tex里搜索DIFadd找到类似这样的定义\providecommand{\DIFadd}[1]{{\protect\color{blue}#1}}想改成红色作为新增色可以追加重定义\renewcommand{\DIFadd}[1]{{\protect\color{red}#1}}删除标注同理\renewcommand{\DIFdel}[1]{{\protect\color{gray}#1}}注意顺序重定义必须放在latexdiff生成的宏定义之后否则会被覆盖。实际操作时我会把自定义放在\begin{document}前最后一块区域确保覆盖生效。4.2 常用--type标记类型latexdiff的--type参数用来决定整体标记风格。我自己最常用的默认值是--typeCFLUA也就是彩色下划线删除线的风格。如果你希望改动看起来更像“修订模式”可以在生成diff.tex时直接指定latexdiff --typeCFLUA old.tex new.tex diff.tex除了CFLUA还支持好几种不同风格比如只上色不画删除线的版本、把整段改动当作一个块来标注的版本等等。具体支持哪些以你当前版本的帮助输出为准latexdiff --help我通常会把日常比较模板固定为CFLUA只有遇到需要严格控制版面样式的场景才去调整。因为--type影响的是所有差异标记的顶层实现换一次就要重新编译验证比较费时间。4.3 生成便于他人审阅的diff如果diff结果要发给不太熟悉LaTeX的人看PDF是更好的载体。但直接发diff.tex和编译后的PDF也有区别diff.tex保留了所有差异宏对方可以自己重新编译PDF则是一张静态图不容易进一步追踪。我建议在这种场景下做两件事。第一给diff.tex单独建一个工作目录避免它和原文件的辅助文件混在一起第二在diff.tex的导言区加一行\renewcommand{\DIFdel}[1]{\textit{#1}}这样删除内容不是被划掉而是以斜体呈现视觉效果更温和。配合颜色调整能在“一眼看出改动”和“不刺眼”之间找到平衡。5. 与Git和在线写作平台配合的自动化方案5.1 latexdiff-vc直接对接版本库如果你用Git管理LaTeX文档那latexdiff还带了一个更省事的姊妹工具latexdiff-vc。它能直接从Git仓库里取出指定版本再自动生成diff文件不需要你手动保存一堆old.tex和new.tex。我习惯这样比较最近两次提交latexdiff-vc -r HEAD~1 -r HEAD --pdf main.tex这条命令会比较HEAD~1和HEAD两个提交里的main.tex生成diff文件并自动编译PDF。如果想比较某个历史提交和当前工作区latexdiff-vc -r HEAD~2 --pdf main.tex这个方式最大的好处是可复现。版本号写进命令里命令写进脚本里每次想追溯某一轮修改直接改版本号就行不用到处翻历史文件。配合--flatten参数多文件项目也能一次完成。5.2 批量对比多个文件有些项目不是单一主文件结构而是拆分得很细。这时候我会写一个简单的shell循环把每个子文件单独拉出来做difffor f in chapter1.tex chapter2.tex chapter3.tex; do latexdiff old_$f new_$f diff_$f done要注意的是各个子文件的diff结果如果都要编译成PDF宏定义和导言区会重复直接整合到一个文档会有冲突。所以批量比较通常用来快速定位改动范围而不是直接生成一个合并PDF。真要生成统一PDF还是要回到主文件加--flatten的方案。5.3 在线协作场景下的处理思路现在很多人直接在某在线LaTeX协作平台上写作平台自带类似“修订记录”的图形化功能看起来比latexdiff更直观。但这类功能往往只能显示平台自己记录的变化无法拿两个本地区文件做任意比较。如果项目要长期离线备份又得在协作平台给合作者展示差异我会把latexdiff生成的PDF上传或转发过去。对方不需要装任何额外工具甚至不需要懂LaTeX打开PDF就能看到红绿标注。相比平台自带的修订逻辑latexdiff的好处是不依赖某个特定平台的存储格式只要手里有两个.tex文件就能随时还原出一次完整的审阅记录。6. 我踩过的坑中文、宏包和数学公式的误报与规避6.1 中文文档的编码与标点中文用户用latexdiff最容易遇到的就是编码问题。现在写LaTeX基本都用UTF-8两个文件编码一致时latexdiff一般能正确处理中文。但如果你拿到的旧文件是GBK编码新文件是UTF-8那生成的diff.tex必然乱码。我的习惯是先统一编码再跑diff。Linux下可以用file命令查看编码然后用iconv转换iconv -f GBK -t UTF-8 old.tex old_utf8.tex另外中文标点和英文标点混用常常产生“假差异”。比如一个句子结尾从半角句号改成全角句号latexdiff会认为整句发生了变化。这不算bug但对审阅者来说很干扰。我的处理方法是把这种纯标点的小改动合并到下一次实质内容修改时一起比较避免单独提交一次diff。6.2 宏包顺序与自定义命令导致的编译失败latexdiff在比较“宏”的时候很机械。如果新版文档调整了宏包的加载顺序或者删除了某个宏包而旧版文档里还在用那个宏包定义的命令diff.tex编译时就会报“undefined control sequence”。处理这类问题我坚持一个原则先用独立脚本确认old.tex和new.tex都能正常编译再跑latexdiff。如果diff.tex编译失败先看报错发生的位置再判断是否与自定义宏有关。有些情况下可以手动在diff.tex导言区补上一段宏定义比如\newcommand{\somecustom}[1]{...}把新旧文档中共同生效的宏定义补齐diff就能编过了。还有一种常见情况新版里把一个命令改名了比如\result改成\res。latexdiff会标出所有替换位置但生成的diff.tex中可能出现\res{...}在新版环境里合法、在旧版环境里不合法的情况。这时候我通常只在diff.tex里保留最核心的文字差异把命令改名带来的噪音手动清理掉。6.3 公式、图表和参考文献的假差异数学公式是latexdiff最容易“过敏”的地方。一个公式只要稍微改变一个符号它可能把整个公式标记为删除再加一个完整的新公式导致PDF里出现大段红蓝区块审阅起来非常吃力。遇到公式密集的文档我会尝试降低数学标记的敏感度。latexdiff提供了--math-markup参数可以调节公式内差异标记的粒度具体取值以当前版本的help为准。如果公式变化确实不重要我甚至会生成一份不带公式差异的diff版本只在正文区域标注修改公式部分保持旧版原文。图表环境也有自己的脾气。图的内容不会真的被diff但图题、标签、\label命令的变化会被标出来。参考文献同理biblatex条目变化需要单独看.bib文件latexdiff能标出的只是\cite里面引用键的变化。遇到这种情况我得手动去检查对应的参考文献条目不能只看diff PDF就完事。6.4 一份快速排查清单症状常见原因处理方式命令找不到发行版精简或PATH未配置安装texlive-extra-utils检查PATH--flatten报错缺少latexpand安装latexpand中文乱码新旧文件编码不一致统一转为UTF-8编译undefined宏包/命令被新版移除手动补全diff.tex导言区宏定义公式大面积标红数学标记粒度过细调整--math-markup或忽略公式差异子文件改动缺失没用--flatten改用--flatten比较主文件这套清单是我每次帮人排查latexdiff问题时一定会过一遍的流程。八成以上的问题最后都能落到这三件事上环境没装完整、文件编码不统一、宏定义不兼容。最后再分享一个小技巧latexdiff生成的diff.tex不要和原始文件放在同一个目录里直接覆盖我一般会新建一个diff_output/目录把生成文件放进去。这样不仅避免clean脚本误删原始文件还能把多轮修改的diff集中归档哪天需要回看“第三轮修改到底改了哪些内容”直接打开对应目录就行。工具本身并不复杂真正有价值的是你把它用成工作流的一部分。