生成索引与参考文献的 makeindex、xindy、bibtex、biber,并不是 LaTeX 的宏,而是独立的可执行程序。因此它们各自决定参数怎么写、返回什么退出码、日志放在哪里。这里还有一个会悄悄搞坏 CI 的事实:makeindex 即使丢弃了条目也返回退出码 0,bibtex 即使警告「找不到这条文献」也返回 0。 失败不写在构建状态里,而写在 .ilg 与 .blg 中。本页从命令一侧而非 LaTeX 一侧来看这四个程序;索引条目的写法与文献数据库的设计另有专页。
bibtex doc 与 makeindex doc.idx——到底哪个要写扩展名
这里没什么可推导的,只能记住:文献类程序不带扩展名,索引类程序带。 bibtex 与 biber 接收的是文档的 job name,然后自己去打开 .aux 或 .bcf。这一点弄错时,提示信息意外地不友好:bibtex doc.tex 会回 I couldn t open file name doc.tex.aux 并以 1 退出;biber doc.tex 则回 ERROR - Cannot find 'doc.tex.bcf'!。两者都只是把扩展名接在你给的名字后面,都不会告诉你问题出在多写了 .tex。相反,makeindex、upmendex、texindy 接收的是输入文件本身,所以要写 doc.idx。想改输出名用 -o,想指定样式用 -s。
bibtex doc # job name, no extension -> reads doc.aux, writes doc.bbl
biber doc # job name, no extension -> reads doc.bcf, writes doc.bbl
makeindex doc.idx # the file itself -> writes doc.ind and doc.ilg
upmendex -o doc.ind doc.idx
texindy -C utf8 -L german-din -o doc.ind doc.idx退出码一览——CI 会在哪里漏掉失败
下表记录的是在本机 TeX Live 2024 上实际跑出来的值。要读出的重点是:警告与失败之间的界线,每个程序划得都不一样。 bibtex 只有在打印了「错误消息」时才返回 2;引用键在 .bib 里找不到,只算警告,状态仍是 0。makeindex 在输入文件不存在时返回 1,但无论丢掉多少条内部条目,都停在 0。也就是说,只盯着 latexmk 或 CI 任务的状态,就可能在索引条目消失、参考文献留空的情况下拿到一个绿色对勾。对索引与文献而言,正确的防守是检查 .ilg 与 .blg,而不是退出码。
| 情况 | 退出码与日志(TeX Live 2024 实测) |
|---|---|
makeindex (entries rejected) | 0。被丢弃的条目只留在 .ilg 里;加上 -q 后连屏幕上也看不到 |
makeindex (no input file) | 1,输出 Input index file nosuch.idx not found. 和一行用法摘要 |
upmendex (no input file) | 255,打印 Nothing written in output file. 与 1 errors, written in doc.ilg. |
bibtex (warnings only) | 0。Warning--I didn t find a database entry for "key" 不算失败 |
bibtex (error messages) | 2,用于 .bib 的语法错误或 I found no database files |
biber | 只有警告时为 0,一旦打印 ERROR - 则为 2;末尾以 INFO - WARNINGS: 1 的形式给出统计 |
读 makeindex 的 .ilg——被丢弃的条目在这里
makeindex 每次运行都会写出 .ind(会被排版的索引)与 .ilg(工作记录)。正常的一次很平淡:Scanning input file doc.idx....done (6 entries accepted, 0 rejected).,接着 Sorting entries....done (19 comparisons).,再接着 Generating output file doc.ind....done (20 lines written, 0 warnings).。说「值得读的只有括号里的数字」几乎不算夸张。若喂给它一份损坏的 .idx,accepted 的数字会变小,原因随之列出:!! Input index error (file = bad.idx, line = 4):,随后是 -- Incomplete first argument (premature LFD).——而退出码依然是 0。只要核对 accepted 的数字与你写下的 \index 条数是否一致,大部分事故就能提前挡住。顺带一提,TeX Live 2024 附带的是 makeindex 2.17,启动时自称 (kpathsea + Thai support)。
makeindex doc.idx
# This is makeindex, version 2.17 [TeX Live 2024] (kpathsea + Thai support).
# Scanning input file doc.idx....done (6 entries accepted, 0 rejected).
# Sorting entries....done (19 comparisons).
# Generating output file doc.ind....done (20 lines written, 0 warnings).
grep -c "^\\\\indexentry" doc.idx # compare this with "entries accepted"
grep "rejected" doc.ilg # the number CI should be watching这个不起眼的程序有着出人意料的来历。它由 Pehong Chen 编写,但其 man 页的致谢里记着 「Leslie Lamport contributed significantly to the design of MakeIndex」。写出 LaTeX 的人深度参与了索引程序的设计——这正是 \index 的写法读起来与 LaTeX 其余部分浑然一体、而非外挂的原因。至于 \index{key@printed} 中 @ 的用法,以及给带重音的词指定排序键这类问题,索引本身那一页有详细说明。
该选哪个索引程序——把同样四个词交给三者排排看
选择标准只有一条:你要做索引的语言需不需要排序规则(collation)? 把 Zeta、Ähre、Apfel、Öl 这四个词原样放进 .idx 交给三个程序,差别一眼就见分晓。makeindex 排出的是 Apfel, Zeta, Ähre, Öl——Ä 与 Ö 的 UTF-8 字节值大于 Z,于是被甩到字母表末尾之后。texindy -C utf8 -L german-din 与 upmendex 都排出 Ähre, Apfel, Öl, Zeta,正如德语 DIN 规则所要求的那样,把 Ä 当作 A、Ö 当作 O。xindy(Joachim Schrod 编写,release 2.5.1)靠语言模块得到这个答案,upmendex(version 1.08)则靠 ICU 74.2 的排序算法。
| 程序 | 如何排列同样的四个词 | 何时选它 |
|---|---|---|
makeindex | Apfel, Zeta, Ähre, Öl — 非 ASCII 落到 Z 之后 | 只有英语,或你打算手工提供排序键时 |
texindy | Ähre, Apfel, Öl, Zeta —— 指定 -L german-din | 欧洲语言;只需在 -L 里写出语言名 |
upmendex | Ähre, Apfel, Öl, Zeta —— 通过 ICU 排序 | 日文与多语混排;仍能读 makeindex 的样式 |
mendex | 猜测输入编码,打印 (guessed encoding #4: UTF-8 = utf8) | 处理旧 pLaTeX 遗产时;新项目请用 upmendex |
两点实务提醒。第一,texindy 写出的 .ind 与 makeindex 的结构不同:它用 \lettergroup 给每个首字母分组加标题,并自行用 \providecommand 写入相应定义。若你的文档重新定义了这些命令就会冲突,所以切换时请先看一眼输出。第二,xindy 跑在 Common Lisp 之上(本版为 CLISP 2.49.93),启动较重,索引一大就能明显感到慢。若掺有日文,upmendex 既快又省心。
bibtex 的 .blg 末尾那份奇怪的清单
跑完 bibtex doc 后打开 doc.blg,越过警告会看到一张陌生的表:if$ -- 47、while$ -- 2、swap$ -- 1、substring$ -- 6……。这是BibTeX 内部栈式机器的各条指令分别被执行了多少次的统计——这一次总共 237 次。之所以存在这种东西,是因为 .bst 样式文件根本不是配置文件,而是给那台虚拟机的程序。同一份 .blg 靠前的位置还有一行 Capacity: max_strings=200000, hash_size=200000, hash_prime=170003,这些数字保留着 1980 年代的内存预算;文献表极大时那声以 Sorry---you ve exceeded BibTeX s 开头的哀鸣,正来自这些上限。作者是斯坦福的 Oren Patashnik,TeX Live 2024 中的版本是 BibTeX 0.99d。
BibTeX 有支持 8 位与 Unicode 的衍生版本,TeX Live 2024 全都带着。bibtex8 自称「8-bit Big BibTeX version 0.99d-x4.02」;bibtexu 是「UTF-8 Big BibTeX」,内建 ICU 74.2。日文方面则有 pbibtex(pTeX 系)与 upbibtex(upTeX 系,自报 upBibTeX 0.99d-j0.36-u1.30 (utf8.uptex))。它们底子都是同一支 0.99d,差别只在字符处理与排序规则。实际中最常见的错误有两类:.bib 格式损坏时会出现 Illegal end of database file---line 14 of file broken.bib 与 I m skipping whatever remains of this entry,退出码为 2;\bibliography 指向的文件不存在时,则出现 I found no database files---while reading file doc.aux。
biber — 每行都标注 INFO / WARN / ERROR 的日志
biber 是用 Perl 写的较新设计,连日志的样貌都全然不同。它以 INFO - This is Biber 2.19 开头,随后逐行报告读入的 .bcf、找到的引用键数量、采用的 locale,以及写出的 .bbl,每行都带 INFO - 标签。出问题时标签会变,例如 WARN - I didn t find a database entry for 'missingkey' (section 0),末尾再给出统计 INFO - WARNINGS: 1。这种机器可读性正是它与 bibtex 最大的实务差别: 在 CI 里只需用 grep 数 WARN - 与 ERROR - 就能写出检查。最常见的事故是输入名写错,biber doc.tex 会打印 ERROR - Cannot find 'doc.tex.bcf'! 并以 2 退出。另外 biber 与 bibtex 都使用 .blg 这个扩展名,两者都试过时请看第一行以免张冠李戴。
biber doc
# INFO - This is Biber 2.19
# INFO - Found 2 citekeys in bib section 0
# INFO - Output to doc.bbl
# WARN - I didn t find a database entry for 'missingkey' (section 0)
# INFO - WARNINGS: 1
# a CI check that the exit code will not give you
grep -c "^WARN -\|^ERROR -" doc.blg
grep "rejected" doc.ilg运行顺序,以及谁替你数遍数
顺序是:排版 → 索引与文献 → 排版 → 再排版。第一遍由 LaTeX 写出 .idx 与 .aux(用 biblatex 时是 .bcf);随后在其上运行这些程序,生成 .ind 与 .bbl;再排版一次把它们读进来;若编号发生位移,还要再来一遍。麻烦之处在于遍数并不固定,latexmk 这类构建工具正因此而存在。手动敲命令,只在需要判断是哪一段出问题时才值得——而这种判断也有固定顺序。先看 .idx 或 .bcf 到底有没有生成:没有,问题在 LaTeX 一侧;接着读 .ilg 与 .blg:有,问题在程序一侧;最后再排版一次,看 .ind 与 .bbl 是否真的进入正文。这三步几乎能把原因收拢到一点。
# the classic Japanese sequence, written out
uplatex paper # writes paper.aux and paper.idx
upbibtex paper # reads paper.aux -> paper.bbl
upmendex paper.idx # reads paper.idx -> paper.ind
uplatex paper # pulls both in
uplatex paper # settles the numbering
# and the same thing delegated
latexmk paper.tex