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 이후의 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를 만듭니다. 두 증상 모두 “1바이트=1문자”라는 pdfTeX의 전제에 다바이트 문자가 들어가지 않는다는 같은 뿌리에서 나옵니다.
listingsutf8을 읽으면 해결된다는 조언이 널리 퍼져 있지만 CJK에는 듣지 않습니다. 이 패키지의 README가 그 이유를 분명히 밝히고 있습니다. 이 우회책은 “파일을 변환할 수 있는 1바이트 인코딩이 존재하는 경우”에만 성립하며, 게다가 \lstinputlisting에만 작용합니다. 유럽 언어의 악센트 문자는 latin1 등으로 내릴 수 있지만, 한자・가나・한글을 담는 1바이트 인코딩은 존재하지 않으므로 변환할 곳이 없습니다. 실제로 \lstinputlisting[inputencoding=utf8/latin1]{sample.py}를 돌려 보면 오류는 사라지는 대신 그 글자들이 출력에서 사라집니다. 조용히 통과한 컴파일이 성공처럼 보이기에 더욱 고약한 증상입니다.
진짜 해법은 엔진을 바꾸는 것입니다. XeLaTeX이나 LuaLaTeX으로 조판하면 두 엔진 모두 입력을 처음부터 유니코드로 다루므로, 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은 조판 도중에 외부 프로그램을 실행하므로, LaTeX에 외부 명령 실행을 허용하는 shell escape가 필요합니다. 허가 없이 돌리면 ! 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 휠 형태로 TeX 배포판 안에 함께 담는 방식으로 바꾸었습니다. 작성자 Geoffrey M. Poore는 이것을 “제한된 shell escape 실행 파일에 대한 LaTeX의 보안 요구 사항에 맞도록 설계했다”고 설명합니다. 그 결과 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가 그 용도의 도구입니다.