在 LaTeX 中排版源代码时,选 listings 还是 minted 并不是配色口味的问题。listings 只用 TeX 宏就实现了语法高亮,它对一门语言的全部认识,就是一份人工写下的关键字清单。minted 则把这件事整个交给用 Python 写成的词法分析器 Pygments,着色精度因此高出一个档次,代价是它必须走到 LaTeX 之外。要质量还是要可移植性——多年来就是这道二选一,直到 minted 3 改写了前提本身。本页就从今天的位置出发,把这两个宏包理清楚。
listings 与 minted 的区别
区别只有一点:由谁来做高亮。listings 完全由纯 LaTeX 宏构成,因此只要写下 \usepackage{listings},在 Overleaf 上、在动不了配置的机房里,都能照常工作。minted 则调用外部程序并读回分析结果,准确度因此胜出,但那个外部程序能否使用就成了前提条件。说到底,二选一其实是在预测:你的文档最终会在哪里编译。
所谓“人工写下的清单”并不是比喻。listings 的语言定义放在 lstlang1.sty 到 lstlang3.sty 三个文件里,内容就是一条条形如 \lst@definelanguage{ACSL}[90]{Fortran}{morekeywords={algorithm,cinterval,...}} 的记录——用逗号隔开的关键字。按 TeX Live 2024 所带的版本清点,约有 95 种语言。而 Pygments 是自 2006 年起由 Georg Brandl 等人开发的独立词法分析库,在 Pygments 2.19 上执行 pygmentize -L lexers,会列出 597 个词法分析器。数量的差距还在其次,原理上就是两回事:一边是词表,一边是按照文法把字符流切成记号的分析器。
| listings | minted | |
|---|---|---|
highlighting | 基于关键字表的近似 | Pygments 的真正词法分析 |
external tools | 无(纯 LaTeX 宏) | Pygments;minted 3 自带 latexminted |
-shell-escape | 不需要 | minted 2 必须;TeX Live 2025 及以后的 minted 3 不需要 |
languages | 约 95 种(TeX Live 2024 所带) | 597 个词法分析器(Pygments 2.19) |
UTF-8 | 在 pdfLaTeX 下以致命错误中止 | 在 pdfLaTeX 下字符被丢弃(仍会生成 PDF) |
listings 基础:lstlisting 环境与 \lstinputlisting
入口只有三个。要在文档里直接写代码,用 lstlisting 环境;要原样引入外部文件,用 \lstinputlisting{sample.py};要在正文中嵌入短片段,用 \lstinline。至于外观,惯例是不要在每次调用时重写,而是在导言区用 \lstset{...} 一次性设定。listings 的选项有一百多个,但实际会碰的,下面这段示例里的十几个就够了。
\usepackage{listings}
\usepackage{xcolor} % needed for the \color{...} styles below
\lstset{
language=Python,
basicstyle=\ttfamily\small, % base font for the code
keywordstyle=\color{blue}\bfseries,
commentstyle=\color{teal}\itshape,
stringstyle=\color{red!60!black},
numbers=left, % line numbers in the left margin
numberstyle=\tiny\color{gray},
frame=single, % draw a thin frame around the block
breaklines=true, % wrap lines that are too long
showstringspaces=false,
tabsize=2,
}
\begin{lstlisting}[caption={Factorial, computed recursively}, label=lst:fact]
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
\end{lstlisting}
% pull in lines 37-45 of an external file, without line numbers
\lstinputlisting[language=C, firstline=37, lastline=45, numbers=none]{sample.c}\lstset 的键按作用分组来记就容易多了。定字体的是 basicstyle(常用 \ttfamily\small);定语义颜色的是 keywordstyle、commentstyle、stringstyle;定周边装饰的是 numbers=left(左侧行号,字体由 numberstyle 决定)和 frame=single(边框)。breaklines=true 用来折回超出版心的长行,忘了它,代码就会直接冲出右边距——这是实务中最常踩的坑。给出 caption= 和 label= 后,代码块就成了与图表同级的「带编号的列表」,可用 \ref{lst:fact} 引用。
导言区的设定随时可以在单个代码块的 [ ] 里覆盖。写成 \begin{lstlisting}[language=C, numbers=none],就只让这一处变成 C 语言且不带行号。对外部文件,firstline= 与 lastline= 还能派上用场,像 \lstinputlisting[firstline=37, lastline=45]{sample.c} 这样只截取需要的行,在实务中很见效——因为不必把代码复制进正文,改动原文件后文档会自动跟着更新。正文内嵌沿用 \verb 的写法:任选一个符号作分隔符,例如 \lstinline|while (i < n)|。
代码里有中日韩文字时为何停在 Invalid UTF-8 byte sequence
这不是宏包的问题,而是引擎的问题。用 pdfLaTeX 编译时,代码里的汉字或谚文在 listings 下会变成致命错误 ! LaTeX Error: Invalid UTF-8 byte sequence,连一页 PDF 都产生不了。换成 minted 也解决不了:它会报 ! LaTeX Error: Unicode character,然后悄悄生成一份缺了那个字的 PDF。两种症状同出一源——多字节字符放不进 pdfTeX「一字节即一字符」的前提。
「加载 listingsutf8 就能解决」这条建议流传很广,但对中日韩文字无效。该宏包的 README 把原因写得很明白:这个变通办法只在「存在某种单字节编码、文件可以转换过去」时才成立,而且只对 \lstinputlisting 生效。欧洲语言的重音字母可以退到 latin1,但没有任何单字节编码能容纳汉字、假名或谚文,也就无处可转。真去跑一遍 \lstinputlisting[inputencoding=utf8/latin1]{sample.py},错误确实消失了,那些字符也一并从输出里消失了。这种失败方式格外阴险,因为编译无声通过看起来就像成功。
真正的解决办法是换引擎。用 XeLaTeX 或 LuaLaTeX 排版,这两个引擎从一开始就把输入当作 Unicode 处理,于是 listings 和 minted 都能原封不动地接受带中文注释的代码。剩下的只是等宽字体里要有这些字,用 fontspec 的 \setmonofont 指定即可。这里有一点要留意:字体往往只覆盖自己那门语言。用日文字体去排简体字或谚文,会出现一串 Missing character 警告,那些字就此消失。若代码里混有多种文字,请挑一款能覆盖全部字符的字体。另外,如果只需要通过几个欧洲语言的重音字母,仍可留在 pdfLaTeX 上用经典办法逐字告诉它:\lstset{literate={é}{{\'e}}1}。
% Compile this with xelatex or lualatex, not pdflatex.
% A monospaced face that actually covers the script you use.
\usepackage{fontspec}
\setmonofont{Noto Sans Mono CJK JP}
\usepackage{listings}
\lstset{basicstyle=\ttfamily\small}
\begin{lstlisting}[language=Python]
def factorial(n):
# a comment written in your own language survives here
return 1 if n <= 1 else n * factorial(n - 1)
\end{lstlisting}minted 基础:\begin{minted}{python} 与 \inputminted
架子和 listings 差不多,区别在于语言是必填参数。语言名写在环境的参数里,如 \begin{minted}{python};外部文件写作 \inputminted{python}{sample.py};正文里的片段写作 \mintinline{python}{print("hi")}。语言之所以不能省,是因为必须先给 Pygments 指定一个词法分析器,分析才谈得上开始。这里没有 listings 那种「导言区定一次、之后就不写」的做法。
\usepackage{minted}
\usemintedstyle{monokai} % one colour theme for the whole document
% \setminted{style=monokai, linenos, fontsize=\small} % broader defaults
\begin{minted}[linenos, bgcolor=black!90, fontsize=\small]{python}
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
\end{minted}
\mint{python}|print("Hello!")| % one line, no environment
\mintinline{python}{print("Hello!")} % inside running text
\inputminted[linenos]{python}{sample.py} % a whole external file
% "text" turns highlighting off without leaving minted
\begin{minted}{text}
plain output, no keywords coloured
\end{minted}选项写在环境名紧后的 [ ] 里,形式为 key=value。常用的有显示行号的 linenos、选择 Pygments 配色的 style=、设背景的 bgcolor=,以及字号 fontsize=。要给全文套用同一配色,用 \usemintedstyle{monokai};要成批设定默认值,用 \setminted{style=monokai, linenos}。遇到 Pygments 不认识的语言,或某处有意不上色,就把语言名写成 text。有一点要提醒:\mint 不是行内命令——它只是省去为一行代码写环境的麻烦;想把代码融进正文,一定要用 \mintinline。
为什么需要 -shell-escape,以及何时不再需要
minted 会在排版过程中启动外部程序,所以需要 shell escape,也就是允许 LaTeX 执行外部命令的权限。没有这个许可就会停在 ! Package minted Error: You must invoke LaTeX with the -shell-escape flag.。用 pdfLaTeX 时加 -shell-escape,用 MiKTeX 时加 -enable-write18。
pdflatex -shell-escape document.tex
xelatex -shell-escape document.tex
# MiKTeX uses the older spelling
pdflatex -enable-write18 document.tex这正是 minted 3 改变的地方。过去你得自己装好 Python 和 Pygments,再打开不受限的 shell escape,而这就是本页开头那道「质量对可移植性」抉择的实质。minted 3 把 Python 那一侧收拢成名为 latexminted 的专用可执行文件,并作为 Python wheel 随 TeX 发行版一同分发。作者 Geoffrey M. Poore 说明它是「按照 LaTeX 对受限 shell escape 可执行文件的安全要求专门设计的」。结果是:在 TeX Live 2025 上,latexminted 已列入受限 shell escape 的许可名单,完全可以不加 -shell-escape 就编译,另装 Pygments 的麻烦也随之消失。
不过,如果本地环境较旧,情况就不同了。TeX Live 2024 所带的是 minted 2.9(2023 年 12 月),这个版本仍然离不开 -shell-escape。自己身处哪一边很好判断:上面那条错误出不出现即可。另外,打开 shell escape 意味着授予该文档执行任意外部命令的权限。切勿对来路不明的 .tex 使用 -shell-escape。 会议和出版社的投稿系统有时干脆禁止 shell escape,也是出于同一理由;投稿前先确认一次文档在不加 -shell-escape 时能否编译通过,会稳妥得多。
要调用外部进程,minted 的编译自然比 listings 慢。补偿这一点的是缓存:minted 把每段高亮好的片段存进工作目录,只要代码不变就不再调用 Pygments。在本机的 TeX Live 2024 上排版 document.tex,会生成一个 _minted-document/ 目录,里面是以代码片段哈希值命名的 .pygtex 文件。第二次以后明显变快,靠的就是这个机制。缓存可以用 cache=false 关掉;当颜色不对,或者换了配色却迟迟不生效时,直接把整个目录删掉是最快的办法。请不要把它纳入版本控制。
到头来该用哪一个
判断其实只有一条轴:这份文档会在哪里编译。如果只在自己机器上,或在 Overleaf 这类打理妥当的环境里排版,minted 的着色明显更好;而 minted 3 配上 TeX Live 2025 及以后的版本,也不必再付出从前那份代价。反过来,若你既管不了合作者的机器,也管不了对方那端的处理链路,那么 listings「不声不响就能跑」这一点,比颜色的精确更值钱。拿不定主意时,按下面的顺序套一遍。
- 装不了外部工具,或用不了 shell escape → listings,没有悬念。全部内容就是
\usepackage{listings}。 - 最看重高亮准确度与语言覆盖面 → minted。基于 Pygments 的着色自成一档。
- 代码里含有中文、日文或韩文 → 该换的是引擎而不是宏包。用 XeLaTeX 或 LuaLaTeX 排版,并让
\setmonofont指向一款覆盖这些文字的等宽字体。 - 不要着色也不要行号,只想按原样输出 →
verbatim或fancyvrb更轻。 - 想写的是伪代码而非可运行的代码 →
algorithm2e和algpseudocode才是专门的工具。