Auf der TeX-Live-2024-Installation, gegen die diese Seite geschrieben wurde, gibt kpsewhich -expand-path='$TEXINPUTS' 8.798 Verzeichnisse aus – rund eine halbe Million Zeichen Pfad. Trotzdem löst sich \usepackage{amsmath} augenblicklich auf. Der Grund ist schlicht: LaTeX schaut in diese Verzeichnisse fast nie hinein. Diese Seite zerlegt die beiden Hälften dieses Kunststücks anhand echter Kommandoausgaben – die TDS (TeX Directory Structure), also die Landkarte aller texmf-Dateien, und kpathsea, die Suchmaschine, die darüber läuft. Welcher Baum überlebt ein Upgrade und welcher wird verworfen? Und warum ist eine Datei in TEXMFHOME der eine Fall, der ohne mktexlsr auskommt?
Die TDS: warum ein Paket über neun Verzeichnisse verstreut ist
Die TDS ordnet Dateien nach Art, nicht nach Paket, und deshalb liegt ein Paket nie an einer einzigen Stelle. Zählt man amsfonts – das Paket hinter amssymb – auf dieser TeX-Live-2024-Installation durch, belegt es neun Verzeichnisse unterhalb von texmf-dist: die Makros in tex/latex/amsfonts/, die kommentierten Quellen .dtx in source/latex/amsfonts/, die Handbücher in doc/fonts/amsfonts/, und die Schriften selbst noch einmal nach Format aufgeteilt auf fonts/tfm/, fonts/type1/, fonts/afm/, fonts/map/ und fonts/source/. Die plain-TeX-Fassung bekommt ihr eigenes 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/amsfontsWarum diese Anordnung? Die Antwort heißt Portabilität. TeX läuft unter macOS, Unix und Windows, und CTAN (das Comprehensive TeX Archive Network) sammelt Tausende von Paketen. Ordnete jede Distribution die Dateien anders an, würden sowohl die Paketautoren als auch die suchenden Werkzeuge jedes Mal stolpern. Die TDS, in den 1990er Jahren von der TeX Users Group (TUG) festgelegt, machte einen Regelsatz weltweit verbindlich: Makros unter tex/, Schriften unter fonts/<Art>/<Anbieter>/<Schrift>/. Damit lässt sich der Ort jeder Datei allein aus den Regeln ableiten, auf jedem Betriebssystem und in jeder Distribution. Unter tex/ folgt noch eine Ebene, tex/<Format>/<Paket>/, wobei <Format> etwa latex, plain oder generic ist.
| Verzeichnis | Inhalt | Gemessene Größe (texmf-dist, TeX Live 2024) |
|---|---|---|
doc/ | Pakethandbücher – das, was texdoc öffnet | 3,7 GB, allein 10.099 PDFs |
fonts/ | Sämtliche Schriftdateien nach Format: tfm, vf, type1, opentype, enc, map | 2,9 GB |
tex/ | Makros, Klassen, Stile (.tex .sty .cls), z. B. tex/latex/... | 594 MB |
source/ | Kommentierte Quellen .dtx samt .ins-Auszugsskripten – die lesbare Implementierung | 426 MB |
scripts/ | Betriebssystemunabhängige Skripte (der eigentliche Code von mktexlsr, latexmk, …) | 133 MB |
bibtex/ | Literaturdatenbanken bib/ und Bibliographiestile bst/ | 26 MB |
web2c/ | Engine-Konfiguration; Heimat von texmf.cnf und der Formatliste fmtutil.cnf | 248 KB |
Falls eine Zeile dieser Tabelle überrascht, dann diese: Die Dokumentation wiegt mehr als die Software. Von den 7,9 GB in texmf-dist entfallen 3,7 GB auf doc/, während die Makros in tex/ gerade 594 MB ausmachen. Deshalb bietet der TeX-Live-Installer überhaupt an, die Dokumentation wegzulassen, und deshalb zerfallen die Docker-Images in Varianten mit und ohne -doc. Wer den Aufbau im Kopf hat, kann ihn außerdem nutzen: Verhält sich ein Paket unerklärlich, liest man source/latex/<Paket>/*.dtx direkt, und das Handbuch, das texdoc öffnet, ist eine reale Datei in doc/.
Welcher Baum ein Upgrade überlebt
Nur texmf-dist wird vollständig ersetzt. TeX Live legt pro Jahr ein Verzeichnis an – /usr/local/texlive/2024 – und packt die eigentliche Distribution, texmf-dist, hinein. Im nächsten Jahr entsteht daneben ein 2025, und texmf-dist wird gegen eine fabrikneue Kopie getauscht. Eigene Dateien in die Distribution zu legen ist deshalb Selbstmord; umgekehrt bleibt alles außerhalb des Jahresverzeichnisses unangetastet. Dass TEXMFLOCAL unter /usr/local/texlive/texmf-local liegt – außerhalb von 2024 –, ist kein Zufall, sondern genau dieser Entwurf. TEXMFHOME liegt noch weiter draußen, im Home-Verzeichnis.
# 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| Variable | Rolle | Was ein Upgrade damit macht |
|---|---|---|
TEXMFDIST | Die Distribution selbst; Tausende Pakete liegen hier. Nicht von Hand bearbeiten | Wird vollständig ersetzt. Alles Hinzugefügte ist weg |
TEXMFLOCAL | Rechnerweite Ergänzungen, von allen Benutzern geteilt | Bleibt erhalten, da außerhalb des Jahresverzeichnisses |
TEXMFHOME | Der persönliche Baum; eigene Klassen und Zeitschriftenstile gehören hierher | Bleibt erhalten; liegt im Home-Verzeichnis und wird nicht berührt |
TEXMFVAR | Automatisch erzeugter Cache: Formate, Font-Maps, LuaTeX-Caches | Wird jährlich neu aufgebaut; Löschen erzwingt nur die Neuerzeugung |
TEXMFCONFIG | Benutzerbezogener Konfigurationsspeicher, beschrieben von updmap und fmtutil | Bleibt erhalten, liegt aber unter einem Jahresverzeichnis |
TEXMFSYSVAR | Systemweites Gegenstück zu VAR / CONFIG, geschrieben von den -sys-Befehlen | TEXMFSYSCONFIG verhält sich gleich; beide liegen im Jahresverzeichnis |
TEXMFROOT | Wurzel der gesamten Installation, /usr/local/texlive/2024 | Ein neues Jahr bedeutet ein völlig anderes Verzeichnis |
Existiert eine Datei gleichen Namens in mehreren Bäumen – welche gewinnt? Das entscheidet eine einzige Variable, TEXMF, deren Wert nichts anderes ist als die Such-Priorität in Reihenfolge. Auf diesem TeX Live 2024 sieht sie wie unten aus: Ganz links gewinnt, also zuerst die eigene Konfiguration und die Caches, dann der persönliche TEXMFHOME, dann das rechnerweite TEXMFLOCAL und zuletzt die Distribution TEXMFDIST. Anders gesagt: Wer mystyle.sty in TEXMFHOME legt, verdeckt damit die gleichnamige Datei der Distribution – nicht durch Überschreiben, sondern durch die natürliche Rangfolge persönlich → Standort → Distribution. Die !!-Markierungen vor einigen Einträgen erklärt der nächste Abschnitt.
$ 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.Darf man texmf-var löschen?
Alles darin ist erzeugt, prinzipiell geht durch Löschen also nichts verloren. Bevor man aber „löschen hilft“ anstimmt, lohnt ein Blick auf den tatsächlichen Inhalt. Auf diesem TeX Live 2024 umfasst das systemseitige texmf-var 259 MB, davon 233 MB in web2c/ mit 53 .fmt-Dateien – pdflatex.fmt allein bringt 7,8 MB. Eine Formatdatei ist ein konserviertes Speicherabbild, das erspart, bei jedem Lauf latex.ltx und die Klassendateien neu zu lesen. Das benutzerseitige texmf-var ist mit 293 MB noch größer, davon 257 MB luatex-cache/, das Ergebnis der Schriftanalyse von LuaTeX. Auch die von updmap geschriebene psfonts.map liegt hier.
$ 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 demandDaraus folgt eine praktische Regel. Verhält sich etwas wegen einer veralteten Formatdatei seltsam, baut man sie mit fmtutil-sys --all neu, statt das Verzeichnis zu löschen. Den ganzen Baum zu löschen ist für engere Fälle gedacht – etwa einen beschädigten LuaTeX-Font-Cache, der luaotfload merkwürdige Fehler ausgeben lässt. Das kostet nur einen langsamen ersten Lauf danach; aber texmf-var nicht mit texmf-config verwechseln: Nimmt man Letzteres mit, sind auch die updmap-Einstellungen weg. Die Befehle zur Neuerzeugung behandelt die Seite zur Paket- und Schriftverwaltung.
Wie kpathsea eine Datei tatsächlich findet
Das Suchen übernimmt eine gemeinsame Bibliothek namens kpathsea (kpath search). Weder pdftex noch xetex, luatex, dvipdfmx oder bibtex suchen selbst; alle fragen kpathsea: „Wo ist amsmath.sty?“ Was kpathsea entgegennimmt, ist eine einzige Zeichenkette mit Regeln darin. Drei Zeichen lohnen sich: $VAR expandiert eine Variable, ein abschließendes // bedeutet „alles darunter, rekursiv“, und ein führendes !! bedeutet „die Platte nicht durchsuchen – nur die im nächsten Abschnitt beschriebene Dateinamendatenbank befragen“. Gibt man TEXINPUTS aus, den Pfad für LaTeX-Quellen, tauchen alle drei auf einmal auf.
$ 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
8798So liest man das: zuerst . (das Verzeichnis mit dem Manuskript), andernfalls rekursiv durch den tex/-Zweig jedes texmf-Baums in der Reihenfolge latex → generic → alles Übrige. Dass eine Datei neben dem Manuskript gewinnt, ist genau die erwartete Intuition – und dort lauert auch die Falle dieses Abschnitts. Bemerkenswert ist außerdem, dass der erste Eintrag in {latex,generic,} sich mit dem Namen des laufenden Programms ändert. Als pdflatex-dev aufgerufen wird daraus {latex-dev,latex,generic,}, der Entwicklungsbaum wird also zuerst befragt. kpathsea antwortet je nachdem, wer fragt. Und die von -expand-path gemeldeten 8.798 sind zugleich eine Warnung: Ohne den Index ls-R müssten bei jeder einzelnen Suche so viele Verzeichnisse geöffnet werden.
ls-R und TEXMFDBS: warum nur TEXMFHOME ohne mktexlsr auskommt
Die Antwort passt in eine Zeile: TEXMFDBS, die Liste der Bäume mit Index, enthält TEXMFHOME nicht. Die 8.798 Verzeichnisse aus dem vorigen Abschnitt jedes Mal zu öffnen kommt nicht in Frage, also legt kpathsea an der Wurzel jedes Baums eine Dateinamendatenbank namens ls-R ab und befragt diese. Welche Bäume einen Index besitzen, sagt TEXMFDBS, und auf diesem TeX Live 2024 sind es genau vier – eben jene Bäume, die in TEXMF ein !! trugen. TEXMFHOME gehört nicht dazu. Deshalb wird TEXMFHOME jedes Mal auf der Platte durchlaufen, und eine dort abgelegte Datei ist sofort auffindbar.
$ 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-onlyDie Datei ls-R selbst ist schmuckloser Text. Ihre erste Zeile lautet stets % ls-R -- filename database for kpathsea; do not change this line., danach folgt Verzeichnis für Verzeichnis der Inhalt. Das texmf-dist/ls-R dieser Maschine ist 5,2 MB groß und 276.953 Zeilen lang; es indiziert 228.764 Dateien in 16.063 Verzeichnissen. Neu aufgebaut wird es mit mktexlsr, und texhash ist ein symbolischer Link darauf – dasselbe Programm unter zweitem Namen. Die praktische Regel ergibt sich sauber: Legt man eine Datei von Hand in TEXMFLOCAL oder einen Systembaum, braucht es mktexlsr; in TEXMFHOME nicht. Das obige Experiment ist der ganze Grund. Welcher Befehl wann greift, behandelt die Seite zur Paket- und Schriftverwaltung.
kpsewhich --all: die von einer älteren Kopie verdeckte Datei finden
kpsewhich --all NAME gibt alle Treffer in Suchreihenfolge aus. Das schlichte kpsewhich liefert nur den ersten – die Datei, die tatsächlich gelesen wird –, für den zweiten und alle weiteren braucht es --all. Der klassische Unfall „es gibt zwei Dateien dieses Namens, und die ältere gewinnt“ wird so mit einem Befehl sichtbar. Selbst in einem frischen TeX Live 2024 existiert amsmath.sty tatsächlich zweimal: die stabile Fassung in tex/latex/amsmath/ und die Entwicklungsfassung in 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.styDiese beiden Läufe sind ein Experiment, das sich ohne jeden Eingriff wiederholen lässt, und sie bestätigen die Regel, dass der erste Treffer gewinnt. In der Praxis beißt sie allerdings fast immer neben dem Manuskript zu. Da TEXINPUTS mit . beginnt, wird eine alte amsmath.sty oder article.cls, die vor Jahren in den Projektordner geriet, vor der aktuellen Kopie der Distribution gelesen. Schlimmer noch, sie versagt in der unangenehmsten Form: Das Dokument baut auf dem eigenen Rechner und nicht auf dem einer Mitautorin. Trifft man auf einen Fehler ohne erkennbare Ursache – ! LaTeX Error: Command \... already defined. und Verwandtes – oder auf Abweichungen zwischen zwei Rechnern, dann zuerst kpsewhich --all tippen. Kürzer kommt man nicht zur Antwort.
kpsewhich benutzen: -var-value gegen -expand-path
-var-value zeigt, was die Konfiguration sagt; -expand-path zeigt, was tatsächlich auf der Platte liegt. Genau diese Lücke macht beide zu Diagnosewerkzeugen. Gibt man TEXMF auf diesem TeX Live 2024 auf beide Arten aus, listet -var-value sieben Bäume samt !!-Markierungen, während -expand-path nur fünf zurückgibt. Die beiden Ausgefallenen – ~/Library/texlive/2024/texmf-config und ~/Library/texmf – existieren schlicht noch nicht. Taucht ein Baum in der Konfiguration, aber nicht in der Expansion auf, gibt es das Verzeichnis also nicht. Wird eine Datei, die sicher in TEXMFHOME liegt, nicht gefunden, ist das der erste Verdacht.
| Befehl | Was er beantwortet | Wann man ihn einsetzt |
|---|---|---|
kpsewhich NAME | Der erste Treffer – die Datei, die wirklich gelesen wird | Damit anfangen: prüfen, ob es die vermutete Datei ist |
kpsewhich --all NAME | Alle Treffer in Suchreihenfolge | Um zu sehen, ob eine ältere Kopie sie verdeckt |
kpsewhich -var-value=TEXMFHOME | Den Wert, den die Konfiguration einer Variablen gibt, samt !! | Um ohne Raten zu prüfen, wo ein Baum liegen soll |
kpsewhich -expand-path=$TEXMF | Die Expansion, beschränkt auf tatsächlich existierende Verzeichnisse | Um die Lücke zwischen Konfiguration und Wirklichkeit zu finden |
kpsewhich -show-path=tex | Die geordnete Verzeichnisliste für diesen Dateityp | Um nachzuvollziehen, warum in dieser Reihenfolge gefunden wird |
texmf.cnf: woher die Variablen ihre Werte bekommen
Jede bisher aufgetauchte Variable – TEXMF, TEXINPUTS, der Ort jedes Baums – steht in einer Konfigurationsdatei namens texmf.cnf. Noch vor allem anderen liest kpathsea sie und übernimmt daraus seine Betriebsparameter: Suchpfade, Lage der Bäume, Speichergrenzen und mehr. Interessant ist, dass es mehr als eine texmf.cnf geben kann. kpathsea liest sie der Reihe nach entlang eines eigenen Suchpfads, TEXMFCNF, und nimmt für jede Variable die erste gefundene Definition – spätere Dateien überschreiben frühere nicht. Auf dieser Maschine liegen zwei übereinander.
$ 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 defaultsDie dünne texmf.cnf oben – die Datei mit den Abweichungen, die TeX Live schreibt – wird zuerst gelesen, die dicke Vorgabendatei danach. Wer einen Wert dauerhaft ändern will, bearbeitet also nicht die Datei der Distribution, sondern schreibt nur die nötigen Zeilen an eine Stelle höherer Priorität. TEXMFLOCAL/web2c/texmf.cnf ist dieser Ort. So überstehen die Einstellungen ein Distributions-Upgrade, und ein Blick auf wenige Zeilen zeigt, was geändert wurde. Zusammengefasst: texmf.cnf legt fest, wo die Bäume liegen und wie die Suchpfade aussehen, und kpathsea findet die Datei dann in dieser Reihenfolge, meist über den Index ls-R. Diese zwei Schichten sind der gesamte Mechanismus hinter der einen stillen Zeile \usepackage{...}.
PATH findet das Programm, kpathsea die Dateien
Das sind zwei völlig verschiedene Mechanismen, und sie zu verwechseln führt die Fehlersuche in die Irre. kpathsea sucht die Dateien, die TeX liest – .sty, .cls, Schriften –, doch zuvor muss die Shell das Programm selbst finden, pdflatex. Das ist Sache des Betriebssystems und besteht darin, die in der Umgebungsvariablen PATH aufgeführten Verzeichnisse der Reihe nach abzugehen. TeX Live sammelt seine ausführbaren Dateien in einem einzigen bin-Verzeichnis pro Betriebssystem und Architektur, und unter macOS stellt MacTeX mit /Library/TeX/texbin einen jahresunabhängigen, stabilen Link bereit. pdflatex: command not found ist also kein kpathsea-Problem, sondern fast sicher ein PATH-Problem. Umgekehrt hat ! LaTeX Error: File 'foo.sty' not found. nichts mit PATH zu tun. Die Einrichtung selbst behandelt die Seite zur Desktop-Installation.
$ which pdflatex
/Library/TeX/texbin/pdflatex
$ readlink /Library/TeX/texbin
Distributions/Programs/texbinWohin mit der eigenen .sty-Datei
Persönliches nach TEXMFHOME, Institutsweites nach TEXMFLOCAL – und in beiden Fällen die TDS-Struktur einhalten. Mehr ist es nicht. Was man nicht tun darf, ist den Ort zu raten: Der Standardwert von TEXMFHOME unterscheidet sich je nach Betriebssystem – ~/texmf unter Linux, aber ~/Library/texmf bei MacTeX unter macOS. Daher beginnt der Weg immer mit kpsewhich -var-value=TEXMFHOME. Umgekehrt darf eine Datei, die nur zu einer Einreichung gehört – eine Konferenz-myconf.cls, ein Journal-journal.sty –, neben dem Manuskript liegen, denn TEXINPUTS schaut zuerst in .. Einen allgemeinen Namen wie article.cls neben das Manuskript zu legen heißt allerdings, den Verdeckungsunfall des vorigen Abschnitts eigenhändig zu bauen.
# 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 itMeldet kpsewhich den erwarteten Pfad, genügt im Manuskript \usepackage{thesisstyle}. Meldet es nichts, sind drei Dinge der Reihe nach zu prüfen. (1) Liegt die Datei unter tex/latex/<Paket>/? TEXINPUTS schaut ausschließlich unterhalb von tex/. (2) Stimmt die Groß- und Kleinschreibung des Dateinamens? (3) Wurde nach dem Ablegen in einem Systembaum mktexlsr ausgeführt? In dieser Reihenfolge geprüft, verwandelt sich das Symptom von „TeX ist kaputt“ in „wohin auf der Suchkarte habe ich es gelegt?“ – eine Frage, die eine Antwort hat.