Archivos de inicio de Zsh: .zprofile frente a .zshrc en macOS y Linux
Traducción automática
Este artículo se tradujo automáticamente a partir de la versión original en inglés.
Si el terminal va lento, o una variable de entorno aparece en un terminal pero no en otro, la causa suele ser el modo de inicio de Zsh. Los dos archivos a los que recurre casi todo el mundo son ~/.zprofile y ~/.zshrc, pero el nombre del archivo por sí solo no dice si una línea debe ir ahí. Este artículo muestra qué archivos de inicio lee cada tipo de Zsh, qué debe ir en cada archivo y qué comandos indican en qué tipo de shell estás.
Zsh elige los archivos de inicio según dos propiedades independientes: si el shell es un shell de login y si es interactivo. Las aplicaciones de terminal de macOS y Linux pueden elegir combinaciones distintas, así que inspecciona el shell en ejecución en lugar de suponer que lo decide el sistema operativo.
Un shell interactivo está pensado para introducir comandos, normalmente desde un terminal, y -i puede forzar ese modo. Un shell de login arranca en modo de login, elegido por su lanzador o por -l; esto no significa que solo arranque cuando inicias sesión en el ordenador (opciones de estado del shell de Zsh).
Zsh evalúa dos indicadores independientes
Un shell puede ser:
- de login e interactivo, como una ventana nueva de Terminal en macOS o
zsh -l - sin login e interactivo, como un
zshanidado que escribes dentro de otro shell - de login y no interactivo, como
zsh -lc 'command' - sin login y no interactivo, como
zsh -c 'command'
El diagrama pone los dos indicadores en dos ejes. Cada celda nombra los programas que suelen iniciar ese tipo de shell y marca cuáles de los cinco archivos de usuario lee.
El programa que inicia el shell elige el modo. Estos son los valores por defecto de los lanzadores habituales:
| Iniciado por | Modo por defecto |
|---|---|
| macOS Terminal, iTerm2 | login, interactivo |
| GNOME Terminal | sin login, interactivo |
| Terminal de VS Code en macOS | login, interactivo (zsh -l) |
| Terminal de VS Code en Linux | sin login, interactivo |
ssh host | login, interactivo |
ssh host 'command' | sin login, no interactivo |
nueva ventana de tmux, con default-command vacío | login, interactivo |
zsh escrito en un shell | sin login, interactivo |
zsh script.zsh | sin login, no interactivo |
Tu configuración puede cambiar cualquier fila. Para ver el modo real del shell actual, ejecuta:
print -r -- "interactive=$options[interactive] login=$options[login]"
El orden real de inicio
Con las opciones de inicio por defecto, Zsh lee primero los archivos globales y después el archivo de usuario correspondiente. El orden de los archivos de usuario es:
$ZDOTDIR/.zshenven cada invocación de Zsh$ZDOTDIR/.zprofilesi el shell es de login$ZDOTDIR/.zshrcsi el shell es interactivo$ZDOTDIR/.zloginsi el shell es de login$ZDOTDIR/.zlogoutcuando un shell interactivo de login termina con normalidad
Si ZDOTDIR no está definida, Zsh usa $HOME. La ubicación de los archivos globales depende de la instalación; los valores habituales son /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin y /etc/zlogout (manual de Zsh).
En macOS, /etc/zprofile ejecuta path_helper, que coloca los directorios del sistema al principio de PATH. Define tus propias entradas de PATH en ~/.zprofile, que se ejecuta después, y no en ~/.zshenv.
RCS es una opción de Zsh, activa por defecto, que permite leer archivos de inicio posteriores a /etc/zshenv. zsh -f la desactiva y normalmente omite los demás archivos de inicio. /etc/zshenv sigue ejecutándose y puede volver a activar RCS. Si el problema desaparece ahí, investiga la configuración de inicio que se ha omitido (manual de Zsh).
El siguiente diagrama muestra el orden completo en una ventana nueva de Terminal en macOS, con los archivos globales incluidos, y qué ocurre con una entrada de PATH añadida en ~/.zshenv frente a ~/.zprofile.
.zprofile se ejecuta una vez por proceso de shell de login, no una vez por inicio de sesión en el ordenador. Si cada pestaña nueva del terminal inicia un shell de login, todas las pestañas lo leen.
Qué va en cada archivo
| Archivo | Regla de selección | Candidatos adecuados | Evitar |
|---|---|---|---|
.zshenv | Todo Zsh | ZDOTDIR y alguna variable poco habitual que necesite todo proceso de Zsh | salida por pantalla, alias, prompts, llamadas de red, comandos lentos |
.zprofile | Shells de login | configuración del path de la sesión de login y valores por defecto exportados | hooks interactivos, completado, atajos de teclado |
.zshrc | Shells interactivos | prompt, completado, alias, opciones del shell, hooks interactivos de herramientas | salida o cambios pensados para scripts |
.zlogin | Shells de login, después de .zshrc | acciones de login poco habituales que deben ejecutarse después de .zshrc | configuración que debe preceder a .zshrc |
.zlogout | Salida de un shell de login | limpieza menor o restablecimiento del terminal | estado importante que deba sobrevivir a fallos o a exec |
Mantén .zshenv corto y silencioso
Todo zsh -c no interactivo lee .zshenv (manual de Zsh). Un echo, una llamada a un gestor de paquetes o un subproceso costoso ahí puede corromper la salida de los comandos y ralentizar los scripts. La mayoría de los usuarios necesita poco o nada en este archivo.
La excepción habitual es ZDOTDIR. Defínela en ~/.zshenv para guardar los demás archivos de inicio en otro directorio, como ~/.config/zsh.
Usa .zprofile para la configuración que solo aplica al login
Una variable exportada en .zprofile la heredan los procesos hijos de ese shell de login. No se inyecta automáticamente en aplicaciones gráficas, servicios o shells no relacionados cuyo proceso padre nunca leyó el archivo.
Un perfil pequeño en macOS puede cargar Homebrew, definir valores por defecto exportados y construir PATH sin duplicados:
# ~/.zprofile
# The Homebrew installer recommends this line on Apple silicon.
eval "$(/opt/homebrew/bin/brew shellenv zsh)"
export EDITOR=nvim
export VISUAL=nvim
# Zsh ties the path array to the PATH scalar.
typeset -U path PATH
path=("$HOME/.local/bin" $path)
export PATH
typeset -U conserva cada entrada una sola vez. Sin él, cada source del archivo o cada shell de login anidado añade de nuevo el mismo directorio, y echo $PATH lo muestra varias veces.
Usa .zshrc para el estado interactivo
Una nueva invocación de zsh hereda las variables exportadas. No hereda los alias, funciones, opciones del shell, atajos de teclado ni hooks del padre. Cada shell interactivo debe definirlos de nuevo, y eso es lo que hace .zshrc. Zsh omite .zshrc en las invocaciones no interactivas normales.
El diagrama compara un zsh anidado y un shell de script zsh -c iniciados desde el mismo shell de login. En este ejemplo, solo .zshrc define el estado del shell que se muestra.
Un .zshrc mínimo define opciones, completado, alias y atajos de teclado:
# ~/.zshrc
setopt auto_cd hist_ignore_all_dups share_history
autoload -Uz compinit
compinit
alias ll='ls -lah'
bindkey -e
Dónde van los hooks de los gestores de versiones
«Pon los gestores de versiones en .zprofile porque son lentos» es un consejo poco fiable. Un gestor de versiones puede generar varios tipos de código:
- una ruta estática o una variable de directorio raíz
- una ruta de shims de comandos que necesitan los procesos hijos
- definiciones de completado
- funciones del shell que modifican el shell actual
- hooks de cambio de directorio para cambiar de versión automáticamente
Los dos primeros se exportan y pasan a los shells hijos. El resto es estado del shell, así que debe existir en cada shell interactivo, incluido un zsh anidado lanzado desde un editor. Por eso la configuración actual de fnm para Zsh pone este hook en .zshrc:
eval "$(fnm env --use-on-cd --shell zsh)"
La configuración actual de pyenv para Zsh también pone la inicialización interactiva completa en .zshrc:
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && path=("$PYENV_ROOT/bin" $path)
eval "$(pyenv init - zsh)"
Pyenv documenta un modo más limitado, pyenv init --path, para configurar la ruta de los shims, mientras que pyenv init completo también instala el completado y las funciones del shell. Revisa la documentación actual de la herramienta y su salida generada antes de dividirla.
Si el arranque es lento, mídelo antes de mover código a un modo en el que deja de funcionar. La sección de diagnóstico explica cómo.
No cargues todo el perfil desde .zshrc
Un truco habitual entre plataformas es este:
# Avoid this blanket coupling.
source ~/.zprofile
Eso convierte acciones que solo corresponden al login en acciones que se ejecutan en cada shell interactivo. Puede iniciar ssh-agent otra vez, pedir de nuevo acceso al llavero, imprimir salida o añadir entradas de PATH por duplicado.
Si tanto los shells de login como los interactivos sin login necesitan un pequeño conjunto de exportaciones estáticas, extrae un fragmento idempotente y cárgalo con source de forma deliberada:
# ~/.config/zsh/environment.zsh
typeset -U path PATH
path=("$HOME/.local/bin" $path)
export PATH EDITOR=nvim VISUAL=nvim
# ~/.zprofile
source "$HOME/.config/zsh/environment.zsh"
# ~/.zshrc
if [[ ! -o login ]]; then
source "$HOME/.config/zsh/environment.zsh"
fi
# Interactive-only configuration follows.
Un shell de login lee environment.zsh a través de .zprofile. Un shell interactivo sin login lo lee a través de .zshrc. Las acciones de login, como ssh-agent, se quedan en .zprofile, así que este patrón separa los datos compartidos de los efectos secundarios del login. Otra opción es configurar el terminal para que inicie un shell de login, de modo que cada pestaña lea .zprofile.
Diagnostica el shell que va lento o al que le falta estado
Empieza por el síntoma: un shell lento o un ajuste que falta.
Reproduce cada modo directamente:
zsh -lic 'print "login interactive"'
zsh -ic 'print "non-login interactive"'
zsh -lc 'print "login non-interactive"'
zsh -c 'print "non-login non-interactive"'
Para un shell lento, usa time para el total, zprof para saber qué función consume el tiempo y la traza -x para saber qué archivo y línea se ejecutan.
Mide el modo que lanza tu terminal:
time zsh -lic exit
time zsh -ic exit
Carga el profiler al principio de .zshrc e imprime su informe al final. El siguiente shell nuevo muestra una tabla de funciones ordenadas por tiempo:
# first line of ~/.zshrc
zmodload zsh/zprof
# last line of ~/.zshrc
zprof
Traza la ejecución por archivo y línea cuando el origen no está claro:
PS4='%N:%i> ' zsh -xlic exit
Ejecuta la traza en una cuenta de prueba limpia o revísala antes de compartirla: los comandos de inicio pueden expandir tokens y otros valores sensibles.
Si falta un ejecutable, inspecciona tanto el modo como el path:
print -r -- "interactive=$options[interactive] login=$options[login]"
print -l -- $path
whence -va python node uv
Para aplicar un cambio, abre una pestaña nueva o ejecuta exec zsh -l (login) o exec zsh (sin login). source ~/.zshrc vuelve a ejecutar el archivo en el shell actual y conserva lo que definió la versión anterior.
Conclusión
La distinción útil no es «entorno frente a alias». Es estado solo de login frente a estado interactivo, más la pequeña parte que debe afectar a todo proceso de Zsh.
Cuando un shell falla, ejecuta el comando print de la primera sección y comprueba después que el ajuste está en un archivo que ese modo lee.