TeX のディレクトリ構成とパス

手元の TeX Live 2024 で kpsewhich -expand-path='$TEXINPUTS' を実行すると、8,798 個のディレクトリ、約 50 万文字のパスが返ってきます。それでも \usepackage{amsmath} は一瞬で解決します。理由は単純で、LaTeX は普段そのディレクトリをほとんど見に行かないからです。このページは TeX の texmf ディレクトリ構成——TDS(TeX Directory Structure)——という置き場所の地図と、kpathsea というその上を走る探索エンジンの二つを、実際のコマンド出力で解剖します。どのツリーがアップグレードで消え、どれが生き残るのか。なぜ TEXMFHOME に置いたファイルだけは mktexlsr なしで見つかるのか。

TDS —— なぜ一つのパッケージが 9 か所に散らばるのか

TDS はファイルを パッケージごとではなく種類ごと に並べます。だから一つのパッケージのファイルは一か所にまとまりません。amssymb を提供する amsfonts を手元の TeX Live 2024 で数えると、texmf-dist の下の 9 つのディレクトリ に散っています。マクロは tex/latex/amsfonts/、注釈つきソースの .dtxsource/latex/amsfonts/、マニュアルの PDF は doc/fonts/amsfonts/、フォントはさらに形式別に fonts/tfm/fonts/type1/fonts/afm/fonts/map/fonts/source/ へ。plain TeX 版だけは tex/plain/amsfonts/ に分かれます。

terminal
$ find /usr/local/texlive/2024/texmf-dist -maxdepth 4 -type d -path '*amsfonts*' | sort
/usr/local/texlive/2024/texmf-dist/doc/fonts/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/afm/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/map/dvips/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/source/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/tfm/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/type1/public/amsfonts
/usr/local/texlive/2024/texmf-dist/source/latex/amsfonts
/usr/local/texlive/2024/texmf-dist/tex/latex/amsfonts
/usr/local/texlive/2024/texmf-dist/tex/plain/amsfonts

なぜこんな分け方をするのか。答えは 移植性 です。TeX は macOS・Unix・Windows で動き、CTAN(Comprehensive TeX Archive Network)には数千のパッケージが集まります。置き場所が配布元ごとに違えば、パッケージを配る側も探す側も毎回つまずきます。TeX ユーザ会(TUG)が 1990 年代にまとめた TDS は、「マクロは tex/ の下」「フォントは fonts/<形式>/<供給元>/<書体>/」という規則を全世界で共有させました。おかげでどの OS でも、どのディストリビューションでも、ファイルの在処が 規則だけから 推測できます。tex/ の下はさらに tex/<フォーマット>/<パッケージ>/ に分かれ、<フォーマット>latexplaingeneric などです。

ディレクトリ中身実測サイズ(TeX Live 2024 の texmf-dist)
doc/パッケージのマニュアル。texdoc が開く先3.7 GB / PDF だけで 10,099 本
fonts/フォント一式。tfmvftype1opentypeencmap と形式ごと2.9 GB
tex/マクロ・クラス・スタイル(.tex .sty .cls)。tex/latex/... など594 MB
source/注釈つきソース .dtx と展開スクリプト .ins。実装が読める426 MB
scripts/OS 非依存の実行スクリプト(mktexlsrlatexmk の本体)133 MB
bibtex/文献データベース bib/ と書誌スタイル bst/26 MB
web2c/エンジンの設定。texmf.cnf とフォーマット一覧 fmtutil.cnf が住む248 KB

表を見て意外に思うところがあるとすれば、説明書が本体より重い ことでしょう。texmf-dist 全体 7.9 GB のうち doc/ が 3.7 GB、マクロ本体の tex/ はわずか 594 MB です。TeX Live のインストーラに「ドキュメントを入れない」という選択肢があるのはこのためで、Docker イメージが -doc 付きと無しに分かれているのも同じ理由です。この配置さえ頭に入っていれば使い道もあります。パッケージの挙動が腑に落ちないときは source/latex/<パッケージ>/*.dtx を直接読みに行けますし、texdoc が開くマニュアルが doc/ にある実ファイルだと分かります。

どのツリーがアップグレードを生き残るか

丸ごと入れ替わるのは texmf-dist だけです。 TeX Live は年ごとのディレクトリ(/usr/local/texlive/2024)を作り、配布物の本体である texmf-dist をその中に置きます。翌年になれば 2025 が隣にでき、texmf-dist は新品に置き換わります。だから配布物の中にファイルを足すのは自殺行為で、逆に言えば 年のディレクトリの外にあるものは無傷 です。TEXMFLOCAL/usr/local/texlive/texmf-local——2024外側——に置かれているのは偶然ではなく、まさにこの設計です。TEXMFHOME はさらに外、ホームディレクトリの中にあります。

terminal
# Never guess these paths - ask. Values below: TeX Live 2024 on macOS.
$ kpsewhich -var-value=TEXMFROOT
/usr/local/texlive/2024
$ kpsewhich -var-value=TEXMFLOCAL      # note: OUTSIDE the year directory
/usr/local/texlive/texmf-local
$ kpsewhich -var-value=TEXMFHOME      # ~/texmf on Linux, ~/Library/texmf on macOS
/Users/you/Library/texmf
$ kpsewhich -var-value=TEXMFVAR
/Users/you/Library/texlive/2024/texmf-var
変数役割アップグレードでどうなるか
TEXMFDIST配布物の本体。数千のパッケージはここ。手で触らない丸ごと入れ替わる。足したものは消える
TEXMFLOCAL計算機全体への追加。全ユーザーが共有する残る。年のディレクトリの外にあるため
TEXMFHOME自分専用のツリー。自作クラスや投稿先のスタイルはここ残る。ホームの中なので影響を受けない
TEXMFVAR自動生成されるキャッシュ。フォーマット・フォント地図・LuaTeX キャッシュ年ごとに作り直される。消えても再生成される
TEXMFCONFIGユーザー単位の設定の置き場。updmapfmtutil が書き込む残るが、年ごとのディレクトリの下にある
TEXMFSYSVAR上の VAR / CONFIG のシステム全体版。-sys 付きコマンドが書くTEXMFSYSCONFIG も同じ。どちらも年のディレクトリの中
TEXMFROOTインストール全体の根。/usr/local/texlive/2024年が変われば別のディレクトリになる

同じ名前のファイルが複数のツリーにあったら、どれが勝つのか。それを決めているのが TEXMF という一本の変数で、中身は探索の 優先順 を並べたリストにすぎません。手元の TeX Live 2024 では下のようになります。左が強く、自分の設定とキャッシュ、次に個人ツリー TEXMFHOME、それから計算機全体の TEXMFLOCAL、最後に配布物 TEXMFDIST の順です。つまり TEXMFHOMEmystyle.sty を置けば、配布物の同名ファイルを覆い隠せます。上書きではなく、個人 → サイト → 配布物という自然な序列です。いくつかの項目の頭に付いている !! の意味は次の節で説明します。

terminal
$ kpsewhich -var-value=TEXMF
{{}/Users/you/Library/texlive/2024/texmf-config,
 /Users/you/Library/texlive/2024/texmf-var,
 /Users/you/Library/texmf,
 !!/usr/local/texlive/texmf-local,
 !!/usr/local/texlive/2024/texmf-config,
 !!/usr/local/texlive/2024/texmf-var,
 !!/usr/local/texlive/2024/texmf-dist}

# Note which entries carry "!!" - and which do not.

texmf-var は消してよいのか

中身はすべて 生成物 なので、原理的には消しても失われるものはありません。ただし「消せば直る」と唱える前に、何が入っているかを見ておく価値はあります。手元の TeX Live 2024 では、システム側の texmf-var が 259 MB。うち 233 MB が web2c/ で、そこには 53 個の .fmt ファイル——pdflatex.fmt だけで 7.8 MB——が並んでいます。フォーマットファイルは、latex.ltx やクラスを毎回読み直さずに済ませるための「メモリの缶詰」です。ユーザー側の texmf-var はさらに大きく 293 MB で、その 257 MB は luatex-cache/、つまり LuaTeX がフォントを解析した結果です。updmap が書き出す psfonts.map もここにあります。

terminal
$ du -sh /usr/local/texlive/2024/texmf-var/*
4.0K    ls-R
 36K    tex
 26M    fonts
233M    web2c          # 53 .fmt files; pdflatex.fmt alone is 7.8 MB

$ du -sh "$(kpsewhich -var-value=TEXMFVAR)"/*
 32K    fonts
2.1M    texdoc
 12M    web2c
 22M    luatexja
257M    luatex-cache   # LuaTeX font analysis, rebuilt on demand

ここから実務の判断が出ます。フォーマットが古いせいで挙動がおかしいときは、ディレクトリを消すのではなく fmtutil-sys --all で作り直すのが定石です。 ディレクトリごと消すのは、LuaTeX のフォントキャッシュが壊れて luaotfload が妙なエラーを吐くときのような、範囲を限れる場面に留めます。全部消しても次回のコンパイルが数十秒遅くなるだけで済みますが、TEXMFCONFIG を巻き込むと updmap の設定まで飛ぶので、texmf-vartexmf-config を混同しないこと。再生成コマンドの詳細はパッケージとフォントの管理のページが持っています。

kpathsea はどうやってファイルを見つけるのか

探索を担当するのは kpathseakpath search)という共有ライブラリで、pdftexxetexluatexdvipdfmxbibtex はどれも自前で探さず、すべて kpathsea に「amsmath.sty はどこか」と尋ねます。kpathsea が受け取るのは 記号を含んだ一本の文字列 です。覚えるべき記号は三つ。$VAR は変数の展開、末尾の // は「この下を再帰的に全部」、先頭の !! は「ディスクを走査せず、次の節で説明するファイル名データベースだけを見ろ」。LaTeX のソースを探す TEXINPUTS を実際に表示すると、その三つが全部出てきます。

terminal
$ kpsewhich -progname=pdflatex -var-value=TEXINPUTS
.:{...the TEXMF list...}/tex/{latex,generic,}//

# The same query, run as a different program:
$ kpsewhich -progname=pdflatex-dev -var-value=TEXINPUTS
.:{...the TEXMF list...}/tex/{latex-dev,latex,generic,}//

# How many real directories does that string stand for?
$ kpsewhich -progname=pdflatex -expand-path='$TEXINPUTS' | tr : '\n' | wc -l
    8798

読み方はこうです。まず .(原稿のあるディレクトリ)、見つからなければ各 texmf ツリーの tex/ の下を latexgeneric → その他の順で再帰的に。原稿のとなりのファイルが最優先というのは直感どおりですし、この節の落とし穴もそこにあります。もう一つ注目したいのは、{latex,generic,} の先頭が 実行しているプログラムの名前で変わる ことです。pdflatex-dev として呼べば {latex-dev,latex,generic,} になり、開発版のツリーが先に見られます。kpathsea は「誰が尋ねてきたか」で答えを変えるわけです。ちなみに -expand-path が返した 8,798 という数字は警告でもあります。ls-R という索引がなければ、一回の探索ごとにこの数のディレクトリを開けにいくことになるからです。

ls-R と TEXMFDBS —— TEXMFHOME だけ mktexlsr が要らない理由

答えは一行で書けます。索引を持つツリーの一覧 TEXMFDBSTEXMFHOME が入っていないからです。 前節の 8,798 ディレクトリを毎回開けるのは論外なので、kpathsea は各ツリーの根に ls-R という ファイル名データベース を置き、そこを引きます。どのツリーが索引を持つのかを列挙しているのが TEXMFDBS で、手元の TeX Live 2024 では四つ——いずれも TEXMF!! が付いていたツリー——しか並びません。TEXMFHOME は入っていない。だから TEXMFHOME は毎回ディスクを見に行き、置いた瞬間から見つかるのです。

terminal
$ kpsewhich -var-value=TEXMFDBS
{!!/usr/local/texlive/texmf-local,
 !!/usr/local/texlive/2024/texmf-config,
 !!/usr/local/texlive/2024/texmf-var,
 !!/usr/local/texlive/2024/texmf-dist}
# TEXMFHOME is absent from this list.

# The experiment: the SAME file, the SAME TDS layout, two different trees.
$ mkdir -p /tmp/t/tex/latex/demo && touch /tmp/t/tex/latex/demo/demo.sty

$ TEXMFHOME=/tmp/t  kpsewhich -progname=pdflatex demo.sty
/tmp/t/tex/latex/demo/demo.sty          # found - no ls-R, no mktexlsr

$ TEXMFLOCAL=/tmp/t kpsewhich -progname=pdflatex demo.sty
$ echo $?
1                                       # NOT found: "!!" means index-only

ls-R の実物は素っ気ないテキストです。冒頭は必ず % ls-R -- filename database for kpathsea; do not change this line. の一行で、以下ディレクトリごとに中身のファイル名が並びます。手元の texmf-dist/ls-R は 5.2 MB・276,953 行で、16,063 個のディレクトリにある 228,764 個のファイルを索引しています。索引を作り直すコマンドは mktexlsr で、texhash はそれへのシンボリックリンク——同じプログラムの別名です。実務上の判断は明快で、TEXMFLOCAL やシステムのツリーに手でファイルを置いたら mktexlsr が要る。TEXMFHOME なら要らない。 上の実験がそのまま理由になっています。コマンドの使い分けの詳細はパッケージとフォントの管理のページへ。

kpsewhich --all —— 古いコピーに隠されたファイルを見つける

kpsewhich --all NAME一致するファイルを探索順に全部 表示します。引数なしの kpsewhich が返すのは一件目、つまり実際に読み込まれるファイルだけなので、二件目以降を見るには --all が要ります。「同じ名前のファイルが二つあって、古いほうが勝っている」という事故は、これで一発で見えます。手元の TeX Live 2024 でも amsmath.sty は実際に二つ存在します——安定版の tex/latex/amsmath/ と、開発版の tex/latex-dev/amsmath/ です。

terminal
$ kpsewhich --all amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex/amsmath/amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex-dev/amsmath/amsmath.sty

# Same two files, opposite order - because the program name changed the path.
$ kpsewhich --all -progname=pdflatex-dev amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex-dev/amsmath/amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex/amsmath/amsmath.sty

この二例は「一件目が勝つ」という規則を、環境をいじらずに確かめられる実験になっています。そして実務でこれが牙をむくのは、たいてい 原稿のとなり です。TEXINPUTS. から始まるので、数年前にどこかから貰った古い amsmath.styarticle.cls がプロジェクトフォルダに紛れ込んでいれば、配布物の新しいコピーより先に読まれます。しかも自分の機械でだけ通り、共著者の環境では通らない、という最悪の形で現れます。! LaTeX Error: Command \... already defined. のような身に覚えのないエラーや、共著者との食い違いに出くわしたら、まず kpsewhich --all を打つ——それが最短の切り分けです。

kpsewhich の使い方 —— -var-value と -expand-path はどう違うのか

-var-value は設定が言っていることを、-expand-path は実際にディスクに在るものを表示します。 この差が診断では効きます。手元の TeX Live 2024 で TEXMF を両方の形で表示すると、-var-value は七つのツリーを !! 付きで並べるのに、-expand-path は五つしか返しません。落ちた二つ——~/Library/texlive/2024/texmf-config~/Library/texmf——は まだ作られていない のです。つまり「設定にはあるのに展開に出てこない」なら、そのディレクトリは存在していません。TEXMFHOME に置いたはずのファイルが見つからないとき、真っ先に疑うべきはこれです。

コマンド何を答えるか使いどころ
kpsewhich NAME実際に読み込まれる一件目のパスまずこれ。「本当にそのファイルか」を確かめる
kpsewhich --all NAME一致する全ファイルを探索順に古いコピーに隠されていないかを見る
kpsewhich -var-value=TEXMFHOME設定が定めている変数の値(!! も見える)ツリーの位置を憶測せずに確かめる
kpsewhich -expand-path=$TEXMF実在するディレクトリだけに展開した結果設定と現実のずれ(作り忘れ)を見つける
kpsewhich -show-path=texそのファイル種別を探す順序つきディレクトリ一覧「なぜこの順で見つかるのか」を追う

texmf.cnf —— 変数の値はどこから来るのか

ここまで出てきた TEXMFTEXINPUTS・各ツリーの場所は、すべて texmf.cnf という設定ファイルに書かれています。kpathsea は何をするより先にこれを読み、探索パス・ツリーの位置・メモリ上限といった動作パラメータを受け取ります。面白いのは、texmf.cnf一つとは限らない ことです。kpathsea は TEXMFCNF という専用の探索パスに沿って複数の texmf.cnf を順に読み、ある変数について 最初に見つけた定義を採用します(後から読むファイルは先の定義を上書きしません)。手元では二つ積み上がっています。

terminal
$ kpsewhich -all texmf.cnf
/usr/local/texlive/2024/texmf.cnf                     # TeX Live's thin override, read first
/usr/local/texlive/2024/texmf-dist/web2c/texmf.cnf    # hundreds of lines of defaults

上の細い texmf.cnf(TeX Live が書き出す差分だけのファイル)が先に読まれ、下の分厚い既定値ファイルは後です。つまり 値を恒久的に変えたいときは、配布物のファイルを編集するのではなく、優先順位の高い場所に必要な行だけを書く のが定石になります。TEXMFLOCAL/web2c/texmf.cnf がその置き場です。こうしておけば設定はディストリビューションのアップグレードを生き延びますし、何を変えたのかも数行を見れば分かります。まとめると、texmf.cnfツリーの位置と探索パスの形 を決め、kpathsea がその順序で(多くは ls-R 経由で)目的のファイルを見つける——\usepackage{...} の一行が静かに解決する仕組みは、この二層でできています。

PATH が探すのはプログラム、kpathsea が探すのはファイル

二つはまったく別の仕組みで、混同すると診断を誤ります。kpathsea が探すのは TeX が 読む ファイル(.sty.cls・フォント)ですが、その前にシェルが 実行ファイルそのもの、つまり pdflatex を見つけなければなりません。こちらは OS の仕事で、環境変数 PATH に並んだディレクトリを順に見るだけです。TeX Live は実行ファイルを OS・アーキテクチャごとの bin ディレクトリ一つにまとめており、macOS の MacTeX は年に依存しない安定リンク /Library/TeX/texbin を用意します。だから pdflatex: command not found は kpathsea の問題ではなく、ほぼ確実に PATH の問題です。逆に ! LaTeX Error: File 'foo.sty' not found.PATH の問題ではありません。設定手順そのものはデスクトップへのインストールのページが持っています。

terminal
$ which pdflatex
/Library/TeX/texbin/pdflatex
$ readlink /Library/TeX/texbin
Distributions/Programs/texbin

自作の .sty はどこに置くか

個人のものは TEXMFHOME、研究室共有のものは TEXMFLOCAL、そしてどちらの場合も TDS の並びを守る。 これだけです。ただし場所を 憶測しないこと——TEXMFHOME の既定値は OS で違い、Linux は ~/texmf ですが macOS の MacTeX は ~/Library/texmf です。だから手順は必ず kpsewhich -var-value=TEXMFHOME から始めます。逆に、投稿先ごとの myconf.clsjournal.sty のように その原稿一式にしか属さない ファイルは、原稿のとなりに置いて構いません。TEXINPUTS. を先に見るからです。ただし article.cls のような 一般的な名前 を原稿の横に置くのは、前節の影の事故を自分で仕込むことになります。

terminal
# Ask for the tree, never hard-code it: this is ~/texmf on Linux,
# ~/Library/texmf on macOS, %USERPROFILE%\texmf on Windows.
HOME_TREE="$(kpsewhich -var-value=TEXMFHOME)"

mkdir -p "$HOME_TREE/tex/latex/thesisstyle"
cp thesisstyle.sty "$HOME_TREE/tex/latex/thesisstyle/"

# Confirm which copy TeX will pick up. No mktexlsr needed for TEXMFHOME.
kpsewhich thesisstyle.sty
kpsewhich --all thesisstyle.sty    # and check nothing else shadows it

kpsewhich が期待どおりのパスを返せば、原稿は \usepackage{thesisstyle} と書くだけで済みます。何も返らないときは、順に三つを疑ってください。(1) ファイルが tex/latex/<パッケージ名>/ の下に入っているか(TEXINPUTStex/ の下しか見ません)。(2) ファイル名の大文字小文字が一致しているか。(3) システムのツリーに置いたのなら mktexlsr を走らせたか。この順で確かめると、症状が「TeX が壊れた」ではなく「探索地図のどこに置いたのか」という、答えの出る問いに変わります。