소스 코드 목록

LaTeX에서 소스 코드를 조판할 때 listingsminted 중 무엇을 고를지는 배색 취향의 문제가 아닙니다. listings는 TeX 매크로만으로 구문 강조를 구현하며, 한 언어에 대해 아는 것이라고는 어떤 단어가 예약어인지를 사람이 손으로 적어 둔 목록뿐입니다. minted는 그 일을 통째로 Python으로 작성된 렉서 Pygments에 맡기므로 색칠의 정확도가 차원이 다르지만, 그 대가로 LaTeX 바깥으로 나가야 합니다. 품질이냐 이식성이냐——오랫동안 이 둘 중 하나였지만, minted 3에서 그 전제 자체가 새로 쓰였습니다. 이 페이지는 지금의 자리에서 두 패키지를 정리합니다.

listingsminted의 차이

차이는 결국 한 가지, 누가 하이라이트를 수행하는가입니다. 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개의 렉서가 나열됩니다. 숫자의 차이보다 중요한 것은 원리의 차이입니다. 한쪽은 단어 목록이고, 다른 쪽은 문법에 따라 문자열을 토큰으로 잘라 내는 해석기입니다.

listingsminted
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-8pdfLaTeX에서는 치명적 오류로 중단pdfLaTeX에서는 문자가 누락(PDF는 생성됨)

listings의 기본: lstlisting 환경과 \lstinputlisting

입구는 셋뿐입니다. 문서에 직접 코드를 쓸 때는 lstlisting 환경, 외부 파일을 그대로 가져올 때는 \lstinputlisting{sample.py}, 본문 중간에 짧은 조각을 넣을 때는 \lstinline을 씁니다. 모양은 호출할 때마다 적는 대신 프리앰블에서 \lstset{...}으로 한 번에 정리해 두는 것이 정석입니다. listings의 옵션은 백 개가 넘지만, 실제로 손대는 것은 아래 예제에 나오는 열몇 개면 충분합니다.

document.tex
\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}처럼 한 글자씩 알려 주는 고전적인 방법도 쓸 수 있습니다.

document.tex
% 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처럼 “프리앰블에서 한 번 정하고 이후에는 생략”하는 방식은 없습니다.

document.tex
\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을 붙여 컴파일하세요.

terminal
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가 더 가볍습니다.
  • 실행되는 코드가 아니라 의사 코드로 알고리즘을 쓰고 싶음algorithm2ealgpseudocode가 그 용도의 도구입니다.