巻末の索引——語と、その語が現れるページ番号を並べた一覧——を、LaTeX は自分では作りません。LaTeX の仕事は本文に置かれた \index{…} の印を拾い、.idx という素の一覧に書き出すところまで。並べ替えて索引の体裁に整えるのは makeindex という別のプログラム です。この分業には由緒があって、makeindex の付属文書は設計に大きく寄与した人物として LaTeX の作者レスリー・ランポートの名を挙げています。このページでは、makeidx と \makeindex の宣言から、! の下位項目や @ のソートキーといった項目の書き方、makeindex を回すビルドの手順、そして「なぜ Ångström が Zulu の後ろに並ぶのか」までを追います。
索引を作る四つの部品 — makeidx は八行しかない
索引作りは四つの部品でできています。プリアンブルで \usepackage{makeidx} と \makeindex を書き、本文の各所に \index{項目} を置き、一覧を出したい場所で \printindex を呼ぶ。意外なのは、この四つのうち \makeindex と \index は LaTeX カーネル(latex.ltx)に元から入っている ことで、makeidx パッケージが足すのは \printindex と、相互参照用の \see・\seealso だけ——実際の中身はわずか八行です。索引の重い仕事は最初から外部プログラムに任せる設計だった、ということがここに現れています。
\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 を何十個も書いたのに何も出ない」ときは、まずこの一行を疑ってください。
\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」を作ります。! を重ねれば入れ子は 3 段(0・1・2 の階層) まで下がります——これは makeindex の設計上の上限です。ソートキーは @。 sortkey@display と書くと 並べ替えに使う文字列と実際に印刷する文字列を切り離せます。\index{alpha@$\alpha$} は索引に α を印刷しつつ「alpha」の位置に並べます。記号や数式のように、字形どおりに並べても意味をなさないものには必須です。
ページ番号の加工は |。 縦棒の後ろに 引数を一つ取る命令の名前(先頭の \ は書かない)を置くと、その項目のそのページ番号だけがその命令で組まれます。\index{cat|textbf} は定義が現れるページを太字にする定番の使い方で、|textit でも自作の命令でもかまいません。ページ範囲は |( と |)。 話題が数ページにまたがるとき、始点に \index{recursion|(}、終点に \index{recursion|)} を置けば 12--15 のような範囲になります。なお makeindex は既定で、連続する 3 ページ以上を自動的に範囲へ縮めます(この自動化を止めるのが -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 と同じく、途中に外部プログラムが挟まる三段構え です。まず LaTeX が \index を集めて mydoc.idx に書き出します(中身は \indexentry{項目}{ページ} の行が並んだだけの素朴なファイルで、開いて読めます)。次に makeindex がそれを並べ替え、組版可能な mydoc.ind に整えます。最後にもう一度 LaTeX を走らせると \printindex が mydoc.ind を読み込み、索引が本文に現れます。処理の記録は mydoc.ilg に残るので、項目の書き方を間違えたときはここを見ます。
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 より後ろ に並びます。
# 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öm を texindy -L english -C utf8 に渡すと、ソートキーなしでも abacus と zebra のあいだ、つまり A の場所に正しく収まります。索引の分量が増えるほど、キーの手入力より照合器を替えるほうが安上がりです。
日本語の索引 — mendex と upmendex
前節の理屈は日本語や中国語にそのまま当てはまり、しかも症状はより深刻です。\index{群} \index{環} \index{体} をそのまま makeindex に渡すと、警告ひとつ出さずに 文字コード順 に並びます——読み(かん・ぐん・たい)とは何の関係もない順序です。エラーにならないので、五十音順のつもりで作った索引が実は無秩序だった、という事故が起こります。ここでの答えは、hyperref のときと同じく 専用の道具に替える ことです。pLaTeX なら mendex、upLaTeX や LuaLaTeX なら upmendex。どちらも makeindex 互換なので、makeindex と打っていたところを置き換えるだけで済みます。
得られるのは かなによる並べ替え です。makeindex 時代は、項目ごとに よみ@表示 の形で読みを与え、濁点や半濁点の扱いまで手で整える必要がありました。upmendex は ICU(International Components for Unicode)の照合を用いてかなを正しく並べるので、その労力の多くが消えます。さらに -d で 辞書ファイル を渡せば漢語の読みをまとめて登録でき、@ による読みの指定そのものを省ける場面が増えます。目安としては、pLaTeX なら mendex、upLaTeX / LuaLaTeX なら upmendex。読みを @ で与えるやり方はどちらでも有効です。
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_positive—headings_flagが正のときに記号グループへ付ける見出し(既定 "Symbols")。delim_0/delim_1/delim_2— 各階層の項目とページ番号のあいだの 区切り(既定はいずれも ", ")。点リーダにもできます。item_0/item_1/item_x1— 項目のあいだ、階層のあいだに挿入する文字列(改行やインデント)。preamble/postamble—.indの先頭と末尾に書き出すコード(既定は\begin{theindex}と\end{theindex})。group_skip— グループの切れ目に入る空き(既定は\indexspace)。
% 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 と複数の索引
imakeidx は makeidx を置き換える形で二つの大きな利点をもたらします。第一に、コンパイル中に 索引プログラムを自動で呼び出す ので、目次と同じ感覚で索引ができます。第二に、一つの文書に 複数の索引 を持てます(事項索引と人名索引を分ける、といった使い方)。設定は \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 を外から呼ぶ従来の三段手順に戻すか、latexmk に任せてください。
\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