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 zsh anidado 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.

Cuadrícula de dos por dos con los indicadores de login e interactivo. Cada celda nombra quién inicia ese shell y marca cuáles de .zshenv, .zprofile, .zshrc, .zlogin y .zlogout lee; la celda interactiva sin login omite .zprofileCuadrícula de dos por dos con los indicadores de login e interactivo. Cada celda nombra quién inicia ese shell y marca cuáles de .zshenv, .zprofile, .zshrc, .zlogin y .zlogout lee; la celda interactiva sin login omite .zprofile

El programa que inicia el shell elige el modo. Estos son los valores por defecto de los lanzadores habituales:

Iniciado porModo por defecto
macOS Terminal, iTerm2login, interactivo
GNOME Terminalsin login, interactivo
Terminal de VS Code en macOSlogin, interactivo (zsh -l)
Terminal de VS Code en Linuxsin login, interactivo
ssh hostlogin, interactivo
ssh host 'command'sin login, no interactivo
nueva ventana de tmux, con default-command vacíologin, interactivo
zsh escrito en un shellsin login, interactivo
zsh script.zshsin 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:

  1. $ZDOTDIR/.zshenv en cada invocación de Zsh
  2. $ZDOTDIR/.zprofile si el shell es de login
  3. $ZDOTDIR/.zshrc si el shell es interactivo
  4. $ZDOTDIR/.zlogin si el shell es de login
  5. $ZDOTDIR/.zlogout cuando 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.

Orden de inicio de un shell de login en macOS, de /etc/zshenv a /etc/zlogout, con ejemplos de PATH: ~/.local/bin añadido en ~/.zshenv acaba el último tras path_helper; añadido en ~/.zprofile se mantiene el primeroOrden de inicio de un shell de login en macOS, de /etc/zshenv a /etc/zlogout, con ejemplos de PATH: ~/.local/bin añadido en ~/.zshenv acaba el último tras path_helper; añadido en ~/.zprofile se mantiene el primero

.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

ArchivoRegla de selecciónCandidatos adecuadosEvitar
.zshenvTodo ZshZDOTDIR y alguna variable poco habitual que necesite todo proceso de Zshsalida por pantalla, alias, prompts, llamadas de red, comandos lentos
.zprofileShells de loginconfiguración del path de la sesión de login y valores por defecto exportadoshooks interactivos, completado, atajos de teclado
.zshrcShells interactivosprompt, completado, alias, opciones del shell, hooks interactivos de herramientassalida o cambios pensados para scripts
.zloginShells de login, después de .zshrcacciones de login poco habituales que deben ejecutarse después de .zshrcconfiguración que debe preceder a .zshrc
.zlogoutSalida de un shell de loginlimpieza menor o restablecimiento del terminalestado 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 shell de login y dos nuevas invocaciones de zsh: ambos reciben una copia de PATH y EDITOR; el zsh anidado vuelve a definir el estado mostrado desde .zshrc; el shell zsh -c omite esa configuraciónUn shell de login y dos nuevas invocaciones de zsh: ambos reciben una copia de PATH y EDITOR; el zsh anidado vuelve a definir el estado mostrado desde .zshrc; el shell zsh -c omite esa configuración

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.

Referencias