색인

책 뒤에 붙는 색인, 곧 낱말과 그 낱말이 나오는 페이지 번호를 늘어놓은 목록을 LaTeX은 스스로 만들지 않습니다. LaTeX의 일은 본문에 놓인 \index{…} 표시를 모아 .idx 라는 날것의 목록으로 써 내는 데서 끝납니다. 그 목록을 정렬해 색인의 모양으로 만드는 것은 makeindex 라는 별도의 프로그램 입니다. 이 분업에는 내력이 있습니다. makeindex의 문서는 그 설계에 크게 기여한 사람으로 LaTeX의 저자 레슬리 램포트를 꼽고 있습니다. 이 페이지에서는 makeidx 패키지와 \makeindex 선언에서 시작해 항목 쓰는 법(! 하위 항목, @ 정렬 키), 빌드 과정, 그리고 “왜 Ångström이 Zulu 뒤에 놓이는가”까지 따라갑니다.

색인을 만드는 네 부품 — makeidx는 여덟 줄뿐

색인 만들기는 네 부품으로 이루어집니다. 프리앰블에 \usepackage{makeidx}\makeindex 를 쓰고, 본문 곳곳에 \index{항목} 을 두고, 목록을 낼 자리에서 \printindex 를 부릅니다. 뜻밖인 것은 이 넷 가운데 둘, 곧 \makeindex\index 는 LaTeX 커널(latex.ltx)에 이미 들어 있다 는 점입니다. makeidx 패키지가 더하는 것은 \printindex 와 상호 참조용 \see, \seealso 뿐이고, 실제 코드는 여덟 줄 남짓입니다. 이 설계가 알려 주는 바가 있습니다. 색인의 무거운 일은 처음부터 LaTeX 바깥에서 하도록 되어 있었다는 것입니다.

  • \usepackage{makeidx}\printindex\see\seealso 를 제공합니다(프리앰블).
  • \makeindex\jobname.idx 를 열고 \index 를 실제로 기록하는 판으로 다시 정의하는 선언입니다(프리앰블 전용). 터미널에는 Writing index file mydoc.idx 라고 나옵니다.
  • \index{항목} — 낱말이 나오는 자리에 두는 표시. 지면에는 아무것도 내지 않고, 그 지점의 페이지 번호만 기록됩니다.
  • \printindex — 완성된 색인을 실제로 조판하는 명령. 사실상 .ind 파일을 읽어 오는 일이며 보통 문서 끝에 둡니다.

\index보이지 않는 표시 라는 점은 강조할 만합니다. 낱말 자체는 여전히 본문에 직접 쓰고, 그 바로 뒤에 \index{…} 를 붙입니다. random numbers\index{random numbers} are used 같은 식입니다. 그리고 중요한 함정이 하나 있습니다. 프리앰블에 \makeindex 를 빠뜨리면 \index인수를 삼키고 아무것도 하지 않습니다. 커널의 기본 정의가 바로 그렇게 되어 있어서 오류도 경고도 나오지 않고 색인만 비게 됩니다. \index 를 수십 개 썼는데 아무것도 나오지 않는다면 먼저 이 한 줄을 의심하십시오.

latex
\documentclass{article}
\usepackage{makeidx}
\makeindex                        % without this line, \index does nothing
\begin{document}

METAFONT\index{METAFONT} draws the shapes,
TeX\index{TeX} sets the type.
We cover random numbers\index{random numbers|textbf} here,
and touch on groups\index{group} and rings\index{ring}.
The treatment of algorithms\index{algorithm|(} starts here ...

% ... several pages later ...
... and the treatment of algorithms\index{algorithm|)} ends here.

\printindex
\end{document}

항목 쓰는 법 — !, @, |, 큰따옴표 네 문자

\index 의 인수에는 작은 고유 문법이 있고, 네 개의 특수 문자로 이루어집니다. 붙들어 둘 점은 이 넷을 해석하는 것이 LaTeX이 아니라 makeindex 라는 사실입니다. LaTeX에게 인수는 그저 문자열이고, 적힌 그대로 .idx 로 흘러 들어갑니다. 그래서 문법을 틀려도 조판 시점에는 아무도 지적하지 않고, makeindex 를 돌려야 비로소 .ilg 기록에 경고로 나타납니다.

하위 항목은 !. 느낌표가 계층을 나눕니다. \index{animals!cats} 는 “animals” 라는 주 항목 아래에 “cats” 를 둡니다. ! 를 거듭하면 더 깊이 들어가 세 단계(0, 1, 2) 까지 내려갑니다. 이것이 makeindex의 설계상 한계입니다. 정렬 키는 @. sortkey@display 로 쓰면 정렬에 쓰는 문자열과 실제로 인쇄되는 문자열을 떼어 놓을 수 있습니다. \index{alpha@$\alpha$} 는 색인에 α 를 인쇄하면서 “alpha” 자리에 배열합니다. 글리프대로 정렬하면 뜻이 통하지 않는 기호나 수식에는 선택이 아니라 필수입니다.

페이지 번호 가공은 |. 세로줄 뒤에 인수를 하나 받는 명령의 이름 을(맨 앞 역슬래시 없이) 두면 그 항목의 그 페이지 번호만 그 명령으로 조판됩니다. \index{cat|textbf} 는 정의가 나오는 페이지를 굵게 만드는 고전적인 용법이고, |textit 이나 직접 만든 명령도 똑같이 됩니다. 페이지 범위는 |(|). 주제가 여러 쪽에 걸치면 시작점에 \index{recursion|(}, 끝점에 \index{recursion|)} 를 두어 12--15 같은 범위를 얻습니다. 또한 makeindex는 기본적으로 연속된 세 쪽 이상을 스스로 범위로 줄입니다. 이 자동 동작을 끄는 것이 -r 옵션입니다.

상호 참조도 | 뒤에 씁니다. \index{dog|see{pets}} 는 페이지 번호 대신 “dog, see pets” 를 내고, |seealso{…} 는 “see also” 를 냅니다. 둘 다 makeidx 가 정의하는 \see\seealso 를 부르는 구조라서, 출력되는 말은 \seename(기본값 “see”)과 \alsoname(기본값 “see also”)을 재정의하면 다른 언어로 바꿀 수 있습니다. 끝으로 큰따옴표는 이스케이프 입니다. !, @, |, 큰따옴표 자체를 평범한 문자로 항목에 넣고 싶으면 바로 앞에 큰따옴표를 둡니다. \index{C"!} 는 “C!” 라는 항목이 됩니다. C 언어나 C++ 의 색인이 대개 여기서 걸립니다.

문자역할쓰는 법
!하위 항목(최대 3단)\index{animals!cats}
@정렬 키: 정렬용과 인쇄용 문자열을 분리\index{alpha@$\alpha$}
|( |)페이지 범위의 시작과 끝\index{recursion|(}\index{recursion|)}
|cmd그 페이지 번호만 명령으로 조판(굵게 등)\index{cat|textbf}
|see |seealso페이지 번호 대신 다른 항목으로 안내\index{dog|see{pets}}
"다음 특수 문자를 보통 문자로 취급\index{C"!} 는 “C!” 가 됨

makeindex 돌리기 — .idx 에서 .ind 로, 그리고 No file mydoc.ind.

색인은 한 번의 컴파일로 완성되지 않습니다. bibtex과 마찬가지로 중간에 외부 프로그램이 끼는 3단 구성 입니다. 먼저 LaTeX이 \index 를 모아 mydoc.idx 에 써 냅니다. \indexentry{항목}{페이지} 행이 늘어선 소박한 파일이라 열어서 읽을 수 있습니다. 다음으로 makeindex 가 그것을 정렬해 조판 가능한 mydoc.ind 로 다듬습니다. 마지막으로 LaTeX을 한 번 더 돌리면 \printindexmydoc.ind 를 읽어 색인이 문서에 나타납니다. 처리 기록은 mydoc.ilg 에 남으므로 항목 문법을 틀렸을 때는 여기를 봅니다.

shell
pdflatex mydoc        # writes mydoc.idx  ("Writing index file mydoc.idx")
makeindex mydoc       # mydoc.idx -> mydoc.ind, log in mydoc.ilg
pdflatex mydoc        # \printindex reads mydoc.ind

# -s picks a style file, -o names the output, -t names the log
makeindex -s style.ist -o mydoc.ind -t mydoc.ilg mydoc.idx

가운데 한 수를 잊었을 때의 증상은 놀랄 만큼 조용합니다. 오류도 경고도 없이 로그에 이 한 줄이 남을 뿐입니다. No file mydoc.ind. 이유는 구조 자체에 있습니다. \printindex 의 실체는 \@input@ 호출이고, 이 명령은 파일이 있으면 읽고 없으면 바로 그 줄을 내놓기 때문입니다. 이 고요함 때문에 색인이 통째로 빠진 문서가 멀쩡해 보이는 것입니다. 다만 실무에서는 latexmk 가 이 왕복을 대신 돌립니다. .idx 가 바뀔 때마다 makeindex 를 부르고 LaTeX을 필요한 횟수만큼 다시 돌리므로, 세 단계를 손으로 칠 일은 점점 줄어듭니다.

왜 Ångström이 Zulu 뒤에 오는가 — makeindex의 정렬 방식

makeindex이 정렬하는 것은 눈에 보이는 낱말이 아니라 정렬 키 이며, @ 를 쓰지 않으면 항목 문자열이 그대로 키가 됩니다. 기본 순서는 문서화되어 있습니다. 기호 → 숫자 → 글자 순이고, 글자끼리는 먼저 대소문자를 구별하지 않고 비교한 뒤, 철자가 완전히 같을 때만 대문자를 앞에 둡니다. 영어를 위한 설계로는 부족함이 없습니다. 문제는 “글자” 의 범위입니다. makeindex에게 글자는 영문자와 숫자뿐입니다. TeX Live 2024에 들어 있는 makeindex 2.17에 Ångströmémile 을 그대로 넘기면, 둘은 A와 E 자리가 아니라 색인의 맨 끝, Zulu 뒤 에 놓입니다.

shell
# entries written with no sort key at all:
#   +plus   9nine   apple   sea lion   seal   Zulu   Angstrom   emile
# (the last two really spelled Ångström and émile)

makeindex mydoc      # default: word ordering
  +plus / 9nine / apple / sea lion / seal / Zulu / Ångström / émile

makeindex -l mydoc   # letter ordering: blanks do not count
  +plus / 9nine / apple / seal / sea lion / Zulu / Ångström / émile

# the fix is an ASCII sort key, not an accented one:
#   \index{Angstrom@Ångström}   files under A
#   \index{emile@émile}         files under E

여기서 두 가지가 따라 나옵니다. 첫째, 악센트가 붙은 낱말에는 ASCII 정렬 키를 주십시오. \index{Angstrom@Ångström} 라고 쓰면 표시는 Ångström 그대로이면서 A 자리에 들어갑니다. 흔한 오해가 있는데, \index{Ångström@Ångström} 로는 아무것도 고쳐지지 않습니다. 키 쪽이 여전히 비 ASCII이기 때문입니다. 둘째, makeindex에는 정렬 방식의 선택지가 있습니다. 기본은 단어 순서(word ordering) 로, 공백이 어떤 글자보다 앞서기 때문에 “sea lion” 이 “seal” 보다 앞에 옵니다. -l 을 붙이면 글자 순서(letter ordering) 가 되어 공백을 아예 무시하고 “seal” 이 앞섭니다. 사전식으로 보이려면 -l, 전화번호부식이면 기본값입니다. 독일어용으로는 DIN 5007을 따르는 -g 도 있습니다.

악센트가 너무 많아 정렬 키를 하나하나 손으로 붙이는 것이 현실적이지 않은 문서라면, 정렬 프로그램 자체를 바꾸는 편이 빠릅니다. xindy(LaTeX 쪽 입구는 texindy)는 다국어 대조 순서를 전제로 설계되었고 TeX Live에 함께 들어 있습니다. 같은 Ångströmtexindy -L english -C utf8 에 넘기면 정렬 키 없이도 abacuszebra 사이, 곧 A 자리에 올바로 자리 잡습니다. 색인이 커질수록 키를 타이핑하는 것보다 대조기를 바꾸는 편이 값이 쌉니다.

일본어 색인 — mendex와 upmendex

앞 절의 논리는 일본어와 중국어에도 그대로 적용되고, 증상은 더 심각합니다. \index{群}, \index{環}, \index{体} 를 그대로 makeindex에 넘기면 경고 하나 없이 문자 코드 순서 로 늘어섭니다. 읽는 소리와는 아무 관계도 없는 순서입니다. 오류가 나지 않으므로, 가나다순으로 만들었다고 생각한 색인이 실은 아무 순서도 아니었다는 사고가 생깁니다. 여기서의 답은 hyperref 때와 마찬가지로 전용 도구로 바꾸는 것 입니다. pLaTeX이면 mendex, upLaTeX이나 LuaLaTeX이면 upmendex 입니다. 둘 다 makeindex 호환이므로 이미 치던 단어를 바꾸기만 하면 됩니다.

얻는 것은 읽는 소리에 따른 정렬 입니다. makeindex 시절에는 항목마다 읽기@표시 형태로 읽기를 주어야 했고 탁음 부호 처리까지 손으로 다듬어야 했습니다. upmendex 는 ICU(International Components for Unicode)의 대조를 써서 가나를 올바로 배열하므로 그 수고의 상당 부분이 사라집니다. 나아가 -d사전 파일 을 넘기면 한자어의 읽기를 한꺼번에 등록할 수 있어, @ 로 읽기를 지정하는 일 자체를 건너뛸 수 있는 경우가 늘어납니다. 기준은 이렇습니다. pLaTeX이면 mendex, upLaTeX / LuaLaTeX이면 upmendex. @ 로 읽기를 주는 방식은 어느 쪽에서나 유효합니다.

shell
uplatex mydoc                 # writes mydoc.idx
upmendex -s style.ist mydoc   # kana sorted via ICU -> mydoc.ind
uplatex mydoc                 # \printindex reads mydoc.ind

# readings can still be given by hand with @, in either program:
#   \index{さくいん@索引}
#   \index{Knuth@クヌース}

색인의 모양 바꾸기 — .ist 스타일 파일

색인의 체재는 스타일 파일(.ist) 이 쥐고 있으며, makeindex -s style.ist mydoc 처럼 -s 로 넘깁니다. 형식은 소박합니다. 매개변수 값 쌍을 늘어놓고, 문자열은 큰따옴표로 감싸며, % 부터 줄 끝까지는 주석입니다. 여기에 적는 것은 LaTeX이 아니라 makeindex에 대한 지시이며, 출력되는 .ind 의 내용을 그대로 결정합니다. mendex / upmendex의 스타일은 makeindex의 것과 상위 호환이므로 기존 .ist 는 그대로 쓸 수 있습니다.

  • headings_flag — 0이 아닌 값으로 두면 그룹이 바뀔 때마다 그룹 제목(A, B… 이나 기호 그룹)을 넣습니다(기본값 0).
  • heading_prefix / heading_suffix — 그 제목 앞뒤에 놓는 문자열.
  • symhead_positiveheadings_flag 가 양수일 때 기호 그룹에 붙이는 제목(기본값 "Symbols").
  • delim_0 / delim_1 / delim_2 — 각 계층의 항목과 페이지 번호 사이의 구분자(모두 기본값 ", "). 점선 리더도 여기에 씁니다.
  • item_0 / item_1 / item_x1 — 항목 사이와 계층 사이에 넣는 문자열(줄바꿈, 들여쓰기).
  • preamble / postamble.ind 의 처음과 끝에 써 내는 코드(기본값은 \begin{theindex}\end{theindex}).
  • group_skip — 그룹이 바뀌는 곳에 들어가는 공백(기본값 \indexspace).
style.ist
% group headings in bold, and a dotted leader before the page number
headings_flag    1
heading_prefix   "{\\bfseries "
heading_suffix   "}\\nopagebreak\n"
delim_0          "\\dotfill "

현대적인 방법 — imakeidx 와 여러 개의 색인

imakeidxmakeidx 를 대신하며 두 가지 큰 이점을 줍니다. 첫째, 컴파일 도중에 색인 프로그램을 자동으로 호출 하므로 목차와 비슷한 감각으로 색인이 만들어집니다. 둘째, 한 문서에 여러 개의 색인 을 둘 수 있습니다. 사항 색인과 인명 색인을 나누는 식입니다. 설정은 \makeindex 에 옵션을 넘기는 방식으로, name= 이 색인을 구별하고, title= 이 제목, intoc 이 목차 수록, program= 이 정렬 프로그램(makeindex / xindy / texindy, 일본어라면 mendex / upmendex), options=-s style.ist 같은 인수 전달을 맡습니다. 색인마다 \makeindex 를 쓰고, 본문에서는 \index[name]{…} 로 나누어 보내며, \printindex[name] 으로 출력합니다.

자동 실행은 셸 이스케이프 위에 서 있고, 바로 그 부분이 환경에 좌우됩니다. TeX Live 2024의 기본 설정에서는 makeindex제한 셸 이스케이프 허용 목록 에 들어 있어 -shell-escape 없이도 imakeidx 가 색인을 완성합니다(kpsewhich -var-value shell_escape_commands 로 자기 환경의 목록을 확인할 수 있습니다). 반면 xindy, texindy, mendex, upmendex 는 그 목록에 없으므로 -shell-escape 가 필요합니다. 셸 이스케이프를 아예 허용하지 않는 투고 시스템이나 엄격한 CI에서는 자동 실행을 쓸 수 없으니, makeindex 를 직접 부르는 3단 절차로 돌아가거나 latexmk 에 맡기십시오.

latex
\documentclass{article}
\usepackage{imakeidx}

% two indexes, built during the compilation
\makeindex[name=subject, title=Subject index, intoc]
\makeindex[name=people,  title=Index of names, intoc,
           options={-s style.ist}]

\begin{document}
Groups\index[subject]{group} matter here.
Knuth\index[people]{Knuth, Donald} wrote TeX.

\printindex[subject]
\printindex[people]
\end{document}

% makeindex runs under restricted shell escape:
%   pdflatex mydoc
% xindy / mendex / upmendex need the full permission:
%   lualatex -shell-escape mydoc