En la instalación de TeX Live 2024 sobre la que se escribió esta página, kpsewhich -expand-path='$TEXINPUTS' imprime 8.798 directorios, unos quinientos mil caracteres de ruta. Y aun así \usepackage{amsmath} se resuelve al instante. El motivo es sencillo: LaTeX casi nunca mira dentro de esos directorios. Esta página desarma las dos mitades de ese truco con salidas de comandos reales: la TDS (TeX Directory Structure), el mapa de dónde vive cada archivo texmf, y kpathsea, el motor de búsqueda que lo recorre. ¿Qué árbol sobrevive a una actualización y cuál se descarta? ¿Y por qué un archivo colocado en TEXMFHOME es el único caso que no necesita mktexlsr?
La TDS: por qué un paquete queda repartido en nueve directorios
La TDS ordena los archivos por tipo, no por paquete, así que un paquete nunca queda en un único sitio. Cuente amsfonts, el paquete que provee amssymb, en esta instalación de TeX Live 2024: ocupa nueve directorios bajo texmf-dist. Las macros están en tex/latex/amsfonts/, el fuente comentado .dtx en source/latex/amsfonts/, los manuales en doc/fonts/amsfonts/, y las fuentes mismas se reparten aún más por formato entre fonts/tfm/, fonts/type1/, fonts/afm/, fonts/map/ y fonts/source/. La versión plain TeX tiene su propio 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¿Por qué organizarlo así? La respuesta es la portabilidad. TeX se ejecuta en macOS, Unix y Windows, y CTAN (Comprehensive TeX Archive Network) reúne miles de paquetes. Si cada distribuidor colocara los archivos a su manera, tanto quienes publican paquetes como las herramientas que los buscan tropezarían cada vez. La TDS, redactada por el TeX Users Group (TUG) en los años noventa, hizo universal un solo conjunto de reglas: las macros bajo tex/, las fuentes bajo fonts/<tipo>/<proveedor>/<tipografía>/. Así la ubicación de cualquier archivo se deduce solo a partir de las reglas, en cualquier sistema y cualquier distribución. Bajo tex/ hay un nivel más, tex/<formato>/<paquete>/, donde <formato> es latex, plain, generic, etcétera.
| Directorio | Contenido | Tamaño medido (texmf-dist, TeX Live 2024) |
|---|---|---|
doc/ | Manuales de paquetes: lo que abre texdoc | 3,7 GB, solo en PDF 10.099 archivos |
fonts/ | Todas las fuentes, por formato: tfm, vf, type1, opentype, enc, map | 2,9 GB |
tex/ | Macros, clases, estilos (.tex .sty .cls), p. ej. tex/latex/... | 594 MB |
source/ | Fuentes comentadas .dtx y sus scripts de extracción .ins: la implementación, legible | 426 MB |
scripts/ | Scripts ejecutables independientes del sistema (el cuerpo de mktexlsr, latexmk, …) | 133 MB |
bibtex/ | Bases bibliográficas bib/ y estilos bst/ | 26 MB |
web2c/ | Configuración de los motores; sede de texmf.cnf y de la lista fmtutil.cnf | 248 KB |
Si alguna fila de la tabla sorprende, es esta: la documentación pesa más que el propio software. De los 7,9 GB de texmf-dist, doc/ se lleva 3,7 GB, mientras que las macros de tex/ apenas suman 594 MB. Por eso el instalador de TeX Live ofrece omitir la documentación, y por eso las imágenes Docker se dividen en variantes con y sin -doc. Conocer la disposición también sirve: cuando un paquete se comporta de forma inexplicable, se va a leer directamente source/latex/<paquete>/*.dtx, y el manual que abre texdoc es un archivo real alojado en doc/.
Qué árbol sobrevive a una actualización
Solo texmf-dist se reemplaza por completo. TeX Live crea un directorio por año —/usr/local/texlive/2024— y coloca dentro la distribución propiamente dicha, texmf-dist. Al año siguiente aparece un 2025 al lado y texmf-dist se sustituye por una copia nueva. Añadir archivos propios a la distribución es, por tanto, suicida; en contrapartida, todo lo que está fuera del directorio del año queda intacto. Que TEXMFLOCAL se encuentre en /usr/local/texlive/texmf-local, fuera de 2024, no es casualidad: es exactamente ese el diseño. TEXMFHOME está aún más afuera, dentro del directorio personal.
# 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 | Función | Qué le hace una actualización |
|---|---|---|
TEXMFDIST | La distribución misma; miles de paquetes. No editar a mano | Se reemplaza por completo. Lo añadido desaparece |
TEXMFLOCAL | Añadidos para toda la máquina, compartidos por todos | Se conserva, por estar fuera del directorio del año |
TEXMFHOME | El árbol personal; sus clases propias y el estilo de una revista van aquí | Se conserva; está en el directorio personal y no se toca |
TEXMFVAR | Caché generado automáticamente: formatos, font maps, cachés de LuaTeX | Se reconstruye cada año; borrarlo solo fuerza su regeneración |
TEXMFCONFIG | Almacén de configuración por usuario, escrito por updmap y fmtutil | Se conserva, pero vive bajo un directorio anual |
TEXMFSYSVAR | Contraparte de todo el sistema de VAR / CONFIG, escrita por los comandos -sys | TEXMFSYSCONFIG funciona igual; ambos están dentro del directorio del año |
TEXMFROOT | Raíz de toda la instalación, /usr/local/texlive/2024 | Un año nuevo significa un directorio distinto |
Cuando un archivo del mismo nombre existe en varios árboles, ¿cuál gana? Lo decide una sola variable, TEXMF, cuyo valor no es más que la prioridad de búsqueda escrita en orden. En este TeX Live 2024 se lee como abajo: gana el de más a la izquierda, así que primero su configuración y sus cachés, después el árbol personal TEXMFHOME, luego el TEXMFLOCAL de la máquina y, en último lugar, la distribución TEXMFDIST. Dicho de otro modo, poner mystyle.sty en TEXMFHOME tapa el archivo homónimo de la distribución: no por sobrescritura, sino por el orden natural personal → sitio → distribución. Las marcas !! delante de algunas entradas se explican en la sección siguiente.
$ 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.¿Se puede borrar texmf-var?
Todo lo que contiene está generado, así que en principio nada se pierde al borrarlo. Antes de entonar «bórralo y se arregla», conviene sin embargo ver qué hay realmente ahí. En este TeX Live 2024 el texmf-var del sistema suma 259 MB, de los cuales 233 MB corresponden a web2c/, que alberga 53 archivos .fmt; solo pdflatex.fmt ocupa 7,8 MB. Un archivo de formato es una imagen de memoria enlatada que evita releer latex.ltx y las clases en cada ejecución. El texmf-var del usuario es aún mayor, 293 MB, de los cuales 257 MB son luatex-cache/, el resultado de que LuaTeX analice las fuentes. El psfonts.map que escribe updmap también vive aquí.
$ 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 demandDe ahí se sigue una regla práctica. Cuando un archivo de formato caducado hace que algo se comporte raro, se reconstruye con fmtutil-sys --all en lugar de borrar el directorio. Borrar el árbol entero vale para casos más acotados: una caché de fuentes de LuaTeX corrompida que hace que luaotfload emita errores extraños, por ejemplo. El precio es solo una primera compilación lenta después; pero no confunda texmf-var con texmf-config: llevarse el segundo por delante supone perder también los ajustes de updmap. Los comandos de regeneración en sí los cubre la página de gestión de paquetes y fuentes.
Cómo encuentra kpathsea un archivo
De la búsqueda se encarga una biblioteca compartida llamada kpathsea (kpath search). Ni pdftex, ni xetex, ni luatex, ni dvipdfmx, ni bibtex buscan por su cuenta: todos preguntan a kpathsea «¿dónde está amsmath.sty?». Lo que kpathsea recibe es una sola cadena con reglas dentro. Vale la pena aprender tres símbolos: $VAR expande una variable, un // final significa «todo lo que hay debajo, recursivamente», y un !! inicial significa «no recorras el disco: consulta solo la base de nombres de archivo que describe la sección siguiente». Imprima TEXINPUTS, la ruta con la que se buscan los fuentes LaTeX, y los tres aparecen a la vez.
$ 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
8798Se lee así: primero . (el directorio del manuscrito) y, si no, recorrer recursivamente la rama tex/ de cada árbol texmf en el orden latex → generic → todo lo demás. Que gane un archivo situado junto al manuscrito es justo lo que cabe esperar, y ahí mismo está la trampa de esta sección. Lo otro que conviene notar es que el primer elemento de {latex,generic,} cambia con el nombre del programa en ejecución. Invocado como pdflatex-dev pasa a ser {latex-dev,latex,generic,}, de modo que se consulta antes el árbol de desarrollo. kpathsea responde según quién pregunte. Y los 8.798 que informó -expand-path son además una advertencia: sin el índice ls-R, esos son los directorios que habría que abrir en cada búsqueda.
ls-R y TEXMFDBS: por qué solo TEXMFHOME no necesita mktexlsr
La respuesta cabe en una línea: TEXMFDBS, la lista de árboles que llevan índice, no incluye TEXMFHOME. Abrir cada vez los 8.798 directorios de la sección anterior queda descartado, así que kpathsea coloca en la raíz de cada árbol una base de nombres de archivo llamada ls-R y la consulta en su lugar. Qué árboles tienen índice lo dice TEXMFDBS, y en este TeX Live 2024 enumera exactamente cuatro: precisamente los que llevaban !! en TEXMF. TEXMFHOME no está entre ellos. Por eso TEXMFHOME se recorre en disco cada vez, y por eso un archivo puesto ahí se encuentra en cuanto aterriza.
$ 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-onlyEl propio archivo ls-R es texto llano y sin adornos. Su primera línea es siempre % ls-R -- filename database for kpathsea; do not change this line., y a continuación se lista cada directorio con su contenido. El texmf-dist/ls-R de esta máquina ocupa 5,2 MB y 276.953 líneas, e indexa 228.764 archivos repartidos en 16.063 directorios. El comando que lo reconstruye es mktexlsr, y texhash es un enlace simbólico a él: el mismo programa con un segundo nombre. La regla práctica sale sola: si coloca un archivo a mano en TEXMFLOCAL o en un árbol del sistema, hace falta mktexlsr; si lo coloca en TEXMFHOME, no. El experimento de arriba es toda la razón. El detalle de los comandos corresponde a la página de gestión de paquetes y fuentes.
kpsewhich --all: hallar el archivo que oculta una copia vieja
kpsewhich --all NOMBRE imprime todas las coincidencias, en orden de búsqueda. El kpsewhich a secas devuelve solo la primera —el archivo que de hecho se leerá—, así que ver la segunda y siguientes requiere --all. El accidente clásico, «hay dos archivos con este nombre y gana el más viejo», se hace visible con un solo comando. Incluso en un TeX Live 2024 recién instalado, amsmath.sty existe realmente por duplicado: la copia estable en tex/latex/amsmath/ y la de desarrollo en 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.styEsas dos ejecuciones son un experimento repetible sin tocar nada, y confirman la regla de que gana el primer resultado. En la práctica, sin embargo, donde esto muerde es casi siempre junto al manuscrito. Como TEXINPUTS empieza por ., un viejo amsmath.sty o article.cls que se coló en la carpeta del proyecto hace años se lee antes que la copia actual de la distribución. Y falla del modo más incómodo posible: el documento compila en su máquina y no en la de un coautor. Ante un error que no se explica —! LaTeX Error: Command \... already defined. y parientes— o ante una discrepancia entre dos máquinas, teclee primero kpsewhich --all. Es el camino más corto a la respuesta.
Usar kpsewhich: -var-value frente a -expand-path
-var-value muestra lo que dice la configuración; -expand-path muestra lo que hay realmente en disco. Esa diferencia es lo que los vuelve útiles para diagnosticar. Imprima TEXMF de ambas formas en este TeX Live 2024: -var-value enumera siete árboles con sus marcas !!, mientras que -expand-path devuelve solo cinco. Los dos que se cayeron —~/Library/texlive/2024/texmf-config y ~/Library/texmf— sencillamente aún no se han creado. Es decir: si un árbol aparece en la configuración pero no en la expansión, ese directorio no existe. Cuando no aparece un archivo que jurarías haber puesto en TEXMFHOME, esa es la primera sospecha.
| Comando | Qué responde | Cuándo usarlo |
|---|---|---|
kpsewhich NAME | La primera coincidencia: el archivo que de verdad se lee | Empiece aquí: confirme que es el archivo que cree |
kpsewhich --all NAME | Todas las coincidencias, en orden de búsqueda | Para ver si una copia vieja la está tapando |
kpsewhich -var-value=TEXMFHOME | El valor que la configuración da a la variable, con las marcas !! | Para confirmar sin adivinar dónde debería estar un árbol |
kpsewhich -expand-path=$TEXMF | La expansión limitada a los directorios que existen de verdad | Para detectar el desfase entre configuración y realidad |
kpsewhich -show-path=tex | La lista ordenada de directorios para ese tipo de archivo | Para rastrear por qué se encuentra en ese orden |
texmf.cnf: de dónde salen los valores de las variables
Todas las variables vistas hasta aquí —TEXMF, TEXINPUTS, la ubicación de cada árbol— están escritas en un archivo de configuración llamado texmf.cnf. Antes que nada, kpathsea lo lee y de ahí toma sus parámetros de funcionamiento: rutas de búsqueda, dónde se sitúa cada árbol, límites de memoria y demás. Lo interesante es que puede haber más de un texmf.cnf. kpathsea los lee en orden a lo largo de una ruta dedicada, TEXMFCNF, y para cada variable toma la primera definición que encuentra; los archivos posteriores no anulan a los anteriores. En esta máquina hay dos apilados.
$ 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 defaultsEl texmf.cnf delgado de arriba —el archivo de diferencias que escribe TeX Live— se lee primero, y el grueso archivo de valores por defecto después. Así que la convención para cambiar un valor de forma permanente es no editar el archivo de la distribución, sino escribir solo las líneas necesarias en un lugar de mayor prioridad. TEXMFLOCAL/web2c/texmf.cnf es ese lugar. Hecho así, los ajustes sobreviven a una actualización de la distribución y unas pocas líneas bastan para ver qué se cambió. En resumen: texmf.cnf fija dónde están los árboles y qué forma tienen las rutas de búsqueda, y kpathsea encuentra después el archivo en ese orden, casi siempre a través del índice ls-R. Esas dos capas son todo el mecanismo detrás de una sola línea callada de \usepackage{...}.
PATH encuentra el programa; kpathsea, los archivos
Son dos mecanismos por completo distintos, y confundirlos descarrila el diagnóstico. kpathsea busca los archivos que TeX lee —.sty, .cls, fuentes—, pero antes de eso el intérprete de órdenes debe encontrar el ejecutable mismo, pdflatex. Eso es cosa del sistema operativo y consiste en recorrer en orden los directorios listados en la variable de entorno PATH. TeX Live reúne sus ejecutables en un único directorio bin por sistema y arquitectura, y en macOS MacTeX ofrece un enlace estable independiente del año en /Library/TeX/texbin. Por tanto pdflatex: command not found no es un problema de kpathsea, sino casi con seguridad de PATH. A la inversa, ! LaTeX Error: File 'foo.sty' not found. no tiene nada que ver con PATH. El procedimiento para configurarlo corresponde a la página de instalación de escritorio.
$ which pdflatex
/Library/TeX/texbin/pdflatex
$ readlink /Library/TeX/texbin
Distributions/Programs/texbinDónde poner su propio archivo .sty
Lo personal va a TEXMFHOME, lo compartido por el laboratorio a TEXMFLOCAL, y en ambos casos se respeta la disposición TDS. Esa es toda la regla. Lo que no debe hacerse es adivinar la ubicación: el valor por defecto de TEXMFHOME cambia según el sistema —~/texmf en Linux, pero ~/Library/texmf en MacTeX bajo macOS—. Así que empiece siempre por kpsewhich -var-value=TEXMFHOME. A la inversa, un archivo que pertenece solo a un envío —un myconf.cls de congreso, un journal.sty de revista— puede quedarse junto al manuscrito, porque TEXINPUTS mira . primero. Pero colocar un nombre genérico como article.cls junto al manuscrito es fabricar con las propias manos el accidente de ocultamiento de la sección anterior.
# 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 itEn cuanto kpsewhich devuelva la ruta esperada, al manuscrito le basta con \usepackage{thesisstyle}. Si no devuelve nada, sospeche de tres cosas por orden. (1) ¿Está el archivo bajo tex/latex/<paquete>/? TEXINPUTS solo mira por debajo de tex/. (2) ¿Coincide el nombre en mayúsculas y minúsculas? (3) Si lo puso en un árbol del sistema, ¿ejecutó mktexlsr? Comprobar en ese orden convierte el síntoma «TeX está roto» en «¿dónde lo puse en el mapa de búsqueda?», una pregunta que sí tiene respuesta.