Файлы запуска Zsh: .zprofile и .zshrc в macOS и Linux
Автоматический перевод Эта статья была автоматически переведена с оригинальной английской версии.
Если терминал работает медленно или переменная окружения появляется в одном терминале, но отсутствует в другом, причина часто связана с режимом запуска Zsh. Чаще всего используют файлы ~/.zprofile и ~/.zshrc, однако само имя файла не говорит, какую именно строку следует туда помещать.
Zsh выбирает файлы запуска по двум независимым признакам: является ли shell login-shell и является ли он интерактивным. Терминальные приложения в macOS и Linux могут выбирать разные сочетания этих режимов, поэтому проверяйте запущенный shell, а не предполагайте, что всё определяется операционной системой.
Коротко. Помещайте инициализацию login-сессии в
.zprofile, а интерактивное поведение — в.zshrc. Если установленаRCS, файл.zshenvдолжен оставаться тихим и минимальным. Не переносите интерактивные хуки менеджеров версий в.zprofileи не подключайте весь profile из.zshrc; изучите сгенерированный код инструмента и следуйте его актуальным инструкциям для Zsh.
Zsh проверяет два независимых флага
Shell может быть:
- login и интерактивным, если Apple Terminal настроен на запуск аккаунта с Default login shell
- не-login и интерактивным — например, это вложенный
zshили один из распространённых вариантов запуска в Linux-терминалах - login и неинтерактивным — например,
zsh -lc 'command' - не-login и неинтерактивным — например,
zsh -c 'command'
Терминальные приложения, IDE, удалённые сессии, мультиплексоры и явные флаги shell могут выбирать разные режимы, поэтому одна только операционная система не определяет результат.
Попросите запущенный shell сообщить режим, а не гадайте:
print -r -- "interactive=$options[interactive] login=$options[login]"
Фактический порядок запуска
Сначала Zsh читает глобальные файлы, а затем соответствующий пользовательский файл. При стандартных RCS options порядок пользовательских файлов такой:
$ZDOTDIR/.zshenv— при каждом запуске Zsh$ZDOTDIR/.zprofile— если shell является login-shell$ZDOTDIR/.zshrc— если shell интерактивный$ZDOTDIR/.zlogin— если shell является login-shell$ZDOTDIR/.zlogout— при штатном завершении login-shell
Если ZDOTDIR не установлена, Zsh использует $HOME. Расположение общесистемных файлов зависит от конкретной установки; часто используются /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin и /etc/zlogout (руководство Zsh).
zsh -f запускается с unset-значением RCS, поэтому обычно пропускает перечисленные выше пользовательские файлы $ZDOTDIR; файл /etc/zshenv читается всегда и может изменить эту опцию.
.zprofile выполняется один раз для каждого процесса login-shell, а не один раз за вход пользователя в компьютер. Если каждая новая вкладка терминала запускает login-shell, каждая вкладка прочитает этот файл.
Что помещать в каждый файл
| Файл | Условие выбора | Подходящие варианты | Чего избегать |
|---|---|---|---|
.zshenv | Каждый Zsh с установленной RCS | Редкие переменные, нужные каждому процессу Zsh | Вывод, алиасы, промпты, сетевые вызовы, медленные команды |
.zprofile | Login-shell | Настройка path для login-сессии и экспортируемые значения по умолчанию | Интерактивные хуки, completion, key bindings |
.zshrc | Интерактивные shell | Промпт, completion, алиасы, опции shell, хуки интерактивных инструментов | Вывод или изменения, предназначенные для скриптов |
.zlogin | Login-shell после .zshrc | Редкие действия после интерактивного запуска | Конфигурация, которая должна предшествовать .zshrc |
.zlogout | Завершение login-shell | Небольшая очистка или сброс состояния терминала | Важное состояние, которое должно пережить сбой или exec |
Пусть .zshenv остаётся скучным, если установлена RCS
Если установлена RCS, каждый неинтерактивный zsh -c читает .zshenv (руководство Zsh). echo, вызов package manager или дорогой subprocess в этом файле могут испортить вывод команд и замедлить скрипты. Большинству пользователей этот файл вообще не нужен либо в нём должно быть совсем немного настроек.
Экспортируемая переменная в .zprofile наследуется дочерними процессами этого login-shell. Она автоматически не передаётся несвязанным GUI-приложениям, сервисам или shell, родительский процесс которого этот файл не читал.
Используйте .zprofile для login-only настройки
Небольшой profile может задать экспортируемые значения по умолчанию и idempotent path:
# ~/.zprofile
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 удаляет дубликаты элементов массива. Это делает повторное подключение файла безопаснее, хотя в файлах запуска всё равно следует избегать лишней работы.
Используйте .zshrc для интерактивного состояния
# ~/.zshrc
setopt auto_cd hist_ignore_all_dups share_history
autoload -Uz compinit
compinit
alias ll='ls -lah'
bindkey -e
Алиасы, completion widgets, key maps, промпты и хуки смены директорий должны находиться в интерактивном shell, который ими пользуется.
Менеджеры версий выходят за границы простой схемы файлов
Совет «помещайте менеджеры версий в .zprofile, потому что они медленные» ненадёжен. Менеджер версий может генерировать несколько типов кода:
- статическую переменную path или root
- путь к command shim, необходимый дочерним процессам
- определения completion
- shell-функции, изменяющие текущий shell
- хуки смены директорий для автоматического переключения версий
Интерактивные части должны присутствовать в каждом интерактивном shell, включая вложенный zsh, запущенный из редактора. Поэтому актуальная настройка fnm для Zsh помещает этот хук в .zshrc:
eval "$(fnm env --use-on-cd --shell zsh)"
Актуальная настройка pyenv для Zsh также помещает полную интерактивную инициализацию в .zshrc:
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && path=("$PYENV_ROOT/bin" $path)
eval "$(pyenv init - zsh)"
Документация Pyenv описывает более узкий режим pyenv init --path для настройки shim-path, тогда как полный pyenv init также устанавливает completion и shell-функции. Перед разделением конфигурации изучите актуальную документацию инструмента и генерируемый им вывод.
Если запуск медленный, сначала измерьте его, а уже потом переносите код в режим, где он перестанет работать.
Не подключайте весь profile из .zshrc
Распространённый кроссплатформенный workaround выглядит так:
# Avoid this blanket coupling.
source ~/.zprofile
Так login-only действия превращаются в действия для каждого интерактивного shell, из-за чего могут повторно запускаться агент, доступ к keychain, вывод или изменение path.
Если и login-, и не-login интерактивным shell нужен небольшой набор статических экспортируемых переменных, вынесите его в idempotent-фрагмент и подключайте явно:
# ~/.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.
Этот паттерн отделяет общие данные от побочных эффектов login-запуска. Другой корректный вариант — единообразно настроить терминал или session manager, вместо того чтобы поддерживать оба режима.
Диагностируйте shell, который работает медленно или теряет состояние
Воспроизведите каждый режим напрямую:
zsh -lic 'print "login interactive"'
zsh -ic 'print "non-login interactive"'
zsh -lc 'print "login non-interactive"'
zsh -c 'print "non-login non-interactive"'
Измерьте именно тот режим, который фактически запускают пользователи:
time zsh -lic exit
time zsh -ic exit
Если источник проблемы неясен, включите trace выполнения файлов и строк:
PS4='%N:%i> ' zsh -xlic exit
Запускайте trace в чистом тестовом аккаунте или проверьте его перед публикацией: команды запуска могут раскрыть токены и другие чувствительные значения.
Если отсутствует исполняемый файл, проверьте и режим, и path:
print -r -- "interactive=$options[interactive] login=$options[login]"
print -l -- $path
whence -va python node uv
Заключение
Полезное различие — не «окружение против алиасов», а состояние, относящееся только к login-shell, и интерактивное состояние, плюс небольшой объём настроек, который должен влиять на каждый процесс Zsh, если установлена RCS.
Проверяйте флаги shell, не допускайте вывода при запуске и размещайте каждый сгенерированный хук там, где доступны все необходимые ему возможности. Так конфигурация будет переносимой между терминальными приложениями — без предположения, что в macOS и Linux существует только один фиксированный режим запуска.