手元の 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/、注釈つきソースの .dtx は source/latex/amsfonts/、マニュアルの PDF は doc/fonts/amsfonts/、フォントはさらに形式別に fonts/tfm/・fonts/type1/・fonts/afm/・fonts/map/・fonts/source/ へ。plain TeX 版だけは tex/plain/amsfonts/ に分かれます。
$ 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/<フォーマット>/<パッケージ>/ に分かれ、<フォーマット> は latex・plain・generic などです。
| ディレクトリ | 中身 | 実測サイズ(TeX Live 2024 の texmf-dist) |
|---|---|---|
doc/ | パッケージのマニュアル。texdoc が開く先 | 3.7 GB / PDF だけで 10,099 本 |
fonts/ | フォント一式。tfm・vf・type1・opentype・enc・map と形式ごと | 2.9 GB |
tex/ | マクロ・クラス・スタイル(.tex .sty .cls)。tex/latex/... など | 594 MB |
source/ | 注釈つきソース .dtx と展開スクリプト .ins。実装が読める | 426 MB |
scripts/ | OS 非依存の実行スクリプト(mktexlsr や latexmk の本体) | 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 はさらに外、ホームディレクトリの中にあります。
# 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 | ユーザー単位の設定の置き場。updmap や fmtutil が書き込む | 残るが、年ごとのディレクトリの下にある |
TEXMFSYSVAR | 上の VAR / CONFIG のシステム全体版。-sys 付きコマンドが書く | TEXMFSYSCONFIG も同じ。どちらも年のディレクトリの中 |
TEXMFROOT | インストール全体の根。/usr/local/texlive/2024 | 年が変われば別のディレクトリになる |
同じ名前のファイルが複数のツリーにあったら、どれが勝つのか。それを決めているのが TEXMF という一本の変数で、中身は探索の 優先順 を並べたリストにすぎません。手元の TeX Live 2024 では下のようになります。左が強く、自分の設定とキャッシュ、次に個人ツリー TEXMFHOME、それから計算機全体の TEXMFLOCAL、最後に配布物 TEXMFDIST の順です。つまり TEXMFHOME に mystyle.sty を置けば、配布物の同名ファイルを覆い隠せます。上書きではなく、個人 → サイト → 配布物という自然な序列です。いくつかの項目の頭に付いている !! の意味は次の節で説明します。
$ 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 もここにあります。
$ 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-var と texmf-config を混同しないこと。再生成コマンドの詳細はパッケージとフォントの管理のページが持っています。
kpathsea はどうやってファイルを見つけるのか
探索を担当するのは kpathsea(kpath search)という共有ライブラリで、pdftex・xetex・luatex・dvipdfmx・bibtex はどれも自前で探さず、すべて kpathsea に「amsmath.sty はどこか」と尋ねます。kpathsea が受け取るのは 記号を含んだ一本の文字列 です。覚えるべき記号は三つ。$VAR は変数の展開、末尾の // は「この下を再帰的に全部」、先頭の !! は「ディスクを走査せず、次の節で説明するファイル名データベースだけを見ろ」。LaTeX のソースを探す TEXINPUTS を実際に表示すると、その三つが全部出てきます。
$ 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/ の下を latex → generic → その他の順で再帰的に。原稿のとなりのファイルが最優先というのは直感どおりですし、この節の落とし穴もそこにあります。もう一つ注目したいのは、{latex,generic,} の先頭が 実行しているプログラムの名前で変わる ことです。pdflatex-dev として呼べば {latex-dev,latex,generic,} になり、開発版のツリーが先に見られます。kpathsea は「誰が尋ねてきたか」で答えを変えるわけです。ちなみに -expand-path が返した 8,798 という数字は警告でもあります。ls-R という索引がなければ、一回の探索ごとにこの数のディレクトリを開けにいくことになるからです。
ls-R と TEXMFDBS —— TEXMFHOME だけ mktexlsr が要らない理由
答えは一行で書けます。索引を持つツリーの一覧 TEXMFDBS に TEXMFHOME が入っていないからです。 前節の 8,798 ディレクトリを毎回開けるのは論外なので、kpathsea は各ツリーの根に ls-R という ファイル名データベース を置き、そこを引きます。どのツリーが索引を持つのかを列挙しているのが TEXMFDBS で、手元の TeX Live 2024 では四つ——いずれも TEXMF で !! が付いていたツリー——しか並びません。TEXMFHOME は入っていない。だから TEXMFHOME は毎回ディスクを見に行き、置いた瞬間から見つかるのです。
$ 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-onlyls-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/ です。
$ 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.sty や article.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 —— 変数の値はどこから来るのか
ここまで出てきた TEXMF・TEXINPUTS・各ツリーの場所は、すべて texmf.cnf という設定ファイルに書かれています。kpathsea は何をするより先にこれを読み、探索パス・ツリーの位置・メモリ上限といった動作パラメータを受け取ります。面白いのは、texmf.cnf が 一つとは限らない ことです。kpathsea は TEXMFCNF という専用の探索パスに沿って複数の texmf.cnf を順に読み、ある変数について 最初に見つけた定義を採用します(後から読むファイルは先の定義を上書きしません)。手元では二つ積み上がっています。
$ 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 の問題ではありません。設定手順そのものはデスクトップへのインストールのページが持っています。
$ 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.cls や journal.sty のように その原稿一式にしか属さない ファイルは、原稿のとなりに置いて構いません。TEXINPUTS が . を先に見るからです。ただし article.cls のような 一般的な名前 を原稿の横に置くのは、前節の影の事故を自分で仕込むことになります。
# 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 itkpsewhich が期待どおりのパスを返せば、原稿は \usepackage{thesisstyle} と書くだけで済みます。何も返らないときは、順に三つを疑ってください。(1) ファイルが tex/latex/<パッケージ名>/ の下に入っているか(TEXINPUTS は tex/ の下しか見ません)。(2) ファイル名の大文字小文字が一致しているか。(3) システムのツリーに置いたのなら mktexlsr を走らせたか。この順で確かめると、症状が「TeX が壊れた」ではなく「探索地図のどこに置いたのか」という、答えの出る問いに変わります。