Ficheiros de arranque do Zsh: .zprofile vs .zshrc no macOS e no Linux

Tradução automática

Este artigo foi traduzido automaticamente a partir da versão original em inglês.

Se o terminal parece lento, ou se uma variável de ambiente aparece num terminal mas não noutro, a causa é muitas vezes o modo de arranque do Zsh. Os dois ficheiros a que a maioria das pessoas recorre são ~/.zprofile e ~/.zshrc, mas o nome do ficheiro, por si só, não diz se uma linha deve ficar ali. Este artigo mostra que ficheiros de arranque cada tipo de Zsh lê, o que pertence a cada ficheiro e os comandos que indicam em que tipo de shell está.

O Zsh escolhe os ficheiros de arranque a partir de duas propriedades independentes: se a shell é uma login shell e se é interativa. As aplicações de terminal no macOS e no Linux podem escolher combinações diferentes, por isso inspecione a shell em execução em vez de presumir que o sistema operativo decide por si.

Uma shell interativa destina-se à introdução de comandos, normalmente a partir de um terminal, e -i pode forçar esse modo. Uma login shell arranca em modo de login, escolhido pelo programa que a inicia ou por -l; isso não significa que só arranca quando inicia sessão no computador (opções de estado da shell do Zsh).

O Zsh avalia duas flags independentes

Uma shell pode ser:

  • login e interativa, como uma nova janela do Terminal do macOS ou zsh -l
  • non-login e interativa, como um zsh aninhado, escrito noutra shell
  • login e não interativa, como zsh -lc 'command'
  • non-login e não interativa, como zsh -c 'command'

O diagrama coloca as duas flags em dois eixos. Cada célula indica os programas que costumam iniciar esse tipo de shell e assinala quais dos cinco ficheiros de utilizador ela lê.

Grelha dois por dois com as flags login e interativo. Cada célula indica quem inicia essa shell e assinala quais de .zshenv, .zprofile, .zshrc, .zlogin e .zlogout ela lê; a célula interativa sem login ignora .zprofileGrelha dois por dois com as flags login e interativo. Cada célula indica quem inicia essa shell e assinala quais de .zshenv, .zprofile, .zshrc, .zlogin e .zlogout ela lê; a célula interativa sem login ignora .zprofile

O programa que inicia a shell escolhe o modo. Estes são os valores por omissão dos lançadores mais comuns:

Iniciada porModo por omissão
Terminal do macOS, iTerm2login, interativa
GNOME Terminalnon-login, interativa
Terminal do VS Code no macOSlogin, interativa (zsh -l)
Terminal do VS Code no Linuxnon-login, interativa
ssh hostlogin, interativa
ssh host 'command'non-login, não interativa
nova janela do tmux, com default-command vaziologin, interativa
zsh escrito numa shellnon-login, interativa
zsh script.zshnon-login, não interativa

As suas definições podem alterar qualquer linha. Para ver o modo real da shell atual, execute:

print -r -- "interactive=$options[interactive] login=$options[login]"

A ordem real de arranque

Com as opções de arranque por omissão, o Zsh lê os ficheiros globais e, depois, o ficheiro de utilizador correspondente. A ordem dos ficheiros de utilizador é:

  1. $ZDOTDIR/.zshenv em todas as invocações do Zsh
  2. $ZDOTDIR/.zprofile se a shell for uma login shell
  3. $ZDOTDIR/.zshrc se a shell for interativa
  4. $ZDOTDIR/.zlogin se a shell for uma login shell
  5. $ZDOTDIR/.zlogout quando uma login shell interativa termina normalmente

Se ZDOTDIR não estiver definida, o Zsh usa $HOME. A localização dos ficheiros de sistema depende da instalação; os valores habituais são /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin e /etc/zlogout (manual do Zsh).

No macOS, /etc/zprofile executa path_helper, que coloca os diretórios do sistema no início de PATH. Defina as suas próprias entradas de PATH em ~/.zprofile, que corre depois dele, e não em ~/.zshenv.

RCS é uma opção do Zsh, ativa por omissão, que permite ler ficheiros de arranque a seguir a /etc/zshenv. zsh -f desativa-a e normalmente ignora os restantes ficheiros de arranque. /etc/zshenv continua a executar-se e pode voltar a ativar RCS. Se um problema desaparecer aí, investigue a configuração de arranque que foi ignorada (manual do Zsh).

O diagrama seguinte mostra a ordem completa numa nova janela do Terminal do macOS, com os ficheiros globais incluídos, e o que acontece a uma entrada de PATH adicionada em ~/.zshenv em comparação com ~/.zprofile.

Ordem de arranque de uma login shell no macOS, de /etc/zshenv a /etc/zlogout, com exemplos de PATH: ~/.local/bin adicionado em ~/.zshenv acaba em último depois do path_helper; adicionado em ~/.zprofile mantém-se em primeiroOrdem de arranque de uma login shell no macOS, de /etc/zshenv a /etc/zlogout, com exemplos de PATH: ~/.local/bin adicionado em ~/.zshenv acaba em último depois do path_helper; adicionado em ~/.zprofile mantém-se em primeiro

O .zprofile corre uma vez por processo de login shell, e não uma vez por início de sessão no computador. Se cada novo separador do terminal iniciar uma login shell, todos os separadores o leem.

O que pertence a cada ficheiro

FicheiroRegra de seleçãoBons candidatosEvitar
.zshenvTodo o ZshZDOTDIR e as raras variáveis de que todos os processos Zsh precisamsaída, aliases, prompts, chamadas de rede, comandos lentos
.zprofileLogin shellsconfiguração do path da sessão de login e valores por omissão exportadoshooks interativos, completions, atalhos de teclado
.zshrcShells interativasprompt, completion, aliases, opções da shell, hooks interativos de ferramentassaída ou alterações destinadas a scripts
.zloginLogin shells, depois do .zshrcações de login raras que têm de correr depois do .zshrcconfiguração que tem de preceder o .zshrc
.zlogoutSaída da login shellpequenas ações de limpeza ou de reposição do terminalestado importante que tem de sobreviver a falhas ou a exec

Mantenha o .zshenv curto e silencioso

Todo o zsh -c não interativo lê o .zshenv (manual do Zsh). Um echo, uma chamada ao gestor de pacotes ou um subprocesso pesado ali pode corromper a saída dos comandos e atrasar os scripts. A maioria dos utilizadores precisa de pouco ou nada neste ficheiro.

A exceção habitual é ZDOTDIR. Defina-a em ~/.zshenv para manter os outros ficheiros de arranque noutro diretório, como ~/.config/zsh.

Use o .zprofile para a configuração só de login

Uma variável exportada em .zprofile é herdada pelos filhos dessa login shell. Não é injetada automaticamente em aplicações gráficas, serviços ou shells sem relação, cujo processo pai nunca leu o ficheiro.

Um perfil pequeno no macOS pode carregar o Homebrew, definir valores exportados por omissão e construir o PATH sem 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 mantém cada entrada uma só vez. Sem ele, cada source do ficheiro ou cada login shell aninhada volta a adicionar o mesmo diretório, e echo $PATH mostra-o várias vezes.

Use o .zshrc para o estado interativo

Uma nova invocação de zsh herda as variáveis exportadas. Não herda aliases, funções, opções da shell, atalhos de teclado nem hooks da shell pai. Cada shell interativa tem de os definir de novo, e é isso que o .zshrc faz. O Zsh ignora .zshrc nas invocações não interativas normais.

O diagrama compara um zsh aninhado e uma shell de script zsh -c iniciados a partir da mesma login shell. Neste exemplo, só .zshrc define o estado da shell apresentado.

Uma login shell e duas novas invocações de zsh: ambas recebem cópias de PATH e EDITOR; o zsh aninhado volta a definir o estado apresentado a partir de .zshrc; a shell zsh -c ignora essa configuraçãoUma login shell e duas novas invocações de zsh: ambas recebem cópias de PATH e EDITOR; o zsh aninhado volta a definir o estado apresentado a partir de .zshrc; a shell zsh -c ignora essa configuração

Um .zshrc mínimo define opções, completion, aliases e atalhos de teclado:

# ~/.zshrc
setopt auto_cd hist_ignore_all_dups share_history

autoload -Uz compinit
compinit

alias ll='ls -lah'
bindkey -e

Onde ficam os hooks dos gestores de versões

“Ponha os gestores de versões no .zprofile porque são lentos” é um conselho pouco fiável. Um gestor de versões pode emitir vários tipos de código:

  • um path estático ou uma variável de raiz
  • um path de shims de comandos de que os processos filhos precisam
  • definições de completion
  • funções de shell que alteram a shell atual
  • hooks de mudança de diretório para a troca automática de versão

Os dois primeiros são exportados e passam para as shells filhas. Os restantes são estado da shell, por isso têm de existir em todas as shells interativas, incluindo um zsh aninhado lançado a partir de um editor. Por isso, a configuração atual do fnm para Zsh põe este hook no .zshrc:

eval "$(fnm env --use-on-cd --shell zsh)"

A configuração atual do pyenv para Zsh também põe a inicialização interativa completa no .zshrc:

export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && path=("$PYENV_ROOT/bin" $path)
eval "$(pyenv init - zsh)"

O pyenv documenta um modo mais restrito, pyenv init --path, para configurar o path dos shims, enquanto o pyenv init completo também instala completion e funções de shell. Consulte a documentação atual da ferramenta e o resultado que ela gera antes de a dividir.

Se o arranque for lento, meça-o antes de mover código para um modo em que deixa de funcionar. A secção de diagnóstico mostra como.

Não carregue o perfil inteiro a partir do .zshrc

Uma solução de contorno comum entre plataformas é:

# Avoid this blanket coupling.
source ~/.zprofile

Isto transforma ações só de login em ações executadas por cada shell interativa. Pode iniciar o ssh-agent outra vez, pedir de novo acesso ao keychain, imprimir saída ou adicionar entradas de PATH duas vezes.

Se as shells interativas, tanto login como non-login, precisarem de um pequeno conjunto de exports estáticos, extraia um fragmento idempotente e carregue-o 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.

Uma login shell lê o environment.zsh através do .zprofile. Uma shell interativa non-login lê-o através do .zshrc. As ações de login, como o ssh-agent, ficam no .zprofile, por isso este padrão mantém os dados partilhados separados dos efeitos secundários do login. Outra opção é configurar o terminal para iniciar uma login shell, de modo que todos os separadores leiam o .zprofile.

Diagnosticar a shell lenta ou sem estado

Comece pelo sintoma: uma shell lenta ou uma definição em falta.

Reproduza cada modo diretamente:

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 uma shell lenta, use time para o total, zprof para saber que função consome o tempo e o trace -x para saber que ficheiro e linha são executados.

Meça o modo que o seu terminal lança:

time zsh -lic exit
time zsh -ic exit

Carregue o profiler no topo do .zshrc e imprima o relatório no fim. A próxima shell nova imprime uma tabela de funções ordenada por tempo:

# first line of ~/.zshrc
zmodload zsh/zprof
# last line of ~/.zshrc
zprof

Faça o trace da execução por ficheiro e linha quando a origem não for clara:

PS4='%N:%i> ' zsh -xlic exit

Execute o trace numa conta de teste limpa ou inspecione-o antes de o partilhar: os comandos de arranque podem expandir tokens e outros valores sensíveis.

Para um executável em falta, inspecione o modo e o path:

print -r -- "interactive=$options[interactive] login=$options[login]"
print -l -- $path
whence -va python node uv

Para aplicar uma alteração, abra um novo separador ou execute exec zsh -l (login) ou exec zsh (non-login). source ~/.zshrc volta a executar o ficheiro na shell atual e mantém tudo o que a versão antiga definiu.

Conclusão

A distinção útil não é “ambiente contra aliases”. É estado só de login contra estado interativo, mais a pequena parte que tem de afetar todos os processos Zsh.

Quando uma shell se comporta mal, execute o comando print da primeira secção e confirme que a definição está num ficheiro que esse modo lê.

Referências