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
zshaninhado, 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ê.
O programa que inicia a shell escolhe o modo. Estes são os valores por omissão dos lançadores mais comuns:
| Iniciada por | Modo por omissão |
|---|---|
| Terminal do macOS, iTerm2 | login, interativa |
| GNOME Terminal | non-login, interativa |
| Terminal do VS Code no macOS | login, interativa (zsh -l) |
| Terminal do VS Code no Linux | non-login, interativa |
ssh host | login, interativa |
ssh host 'command' | non-login, não interativa |
nova janela do tmux, com default-command vazio | login, interativa |
zsh escrito numa shell | non-login, interativa |
zsh script.zsh | non-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 é:
$ZDOTDIR/.zshenvem todas as invocações do Zsh$ZDOTDIR/.zprofilese a shell for uma login shell$ZDOTDIR/.zshrcse a shell for interativa$ZDOTDIR/.zloginse a shell for uma login shell$ZDOTDIR/.zlogoutquando 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.
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
| Ficheiro | Regra de seleção | Bons candidatos | Evitar |
|---|---|---|---|
.zshenv | Todo o Zsh | ZDOTDIR e as raras variáveis de que todos os processos Zsh precisam | saída, aliases, prompts, chamadas de rede, comandos lentos |
.zprofile | Login shells | configuração do path da sessão de login e valores por omissão exportados | hooks interativos, completions, atalhos de teclado |
.zshrc | Shells interativas | prompt, completion, aliases, opções da shell, hooks interativos de ferramentas | saída ou alterações destinadas a scripts |
.zlogin | Login shells, depois do .zshrc | ações de login raras que têm de correr depois do .zshrc | configuração que tem de preceder o .zshrc |
.zlogout | Saída da login shell | pequenas ações de limpeza ou de reposição do terminal | estado 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.
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ê.