Открыл терминал на свежем Mac - и вот он, голый zsh. Скучный prompt "user@MacBook ~ %", никакого автодополнения по делу, история теряется между сессиями, а после установки Homebrew половина команд "command not found". Знакомо? Начиная с macOS Catalina zsh - оболочка по умолчанию вместо bash, и Apple больше его не двигает в сторону. Значит, разбираться надо именно с ним. Хорошая новость: zsh из коробки мощнее bash, и грамотная настройка терминала Mac превращает его из печатной машинки в рабочий инструмент - с подсказками, подсветкой, нормальной историей и осмысленным prompt.
Но прежде чем копировать чужой zshrc с гитхаба, надо понять механику: какой файл когда читается. Девять из десяти проблем с PATH, дублирующимися алиасами и тормозами при старте - это непонимание порядка загрузки. Разберём так, чтобы ты потом не гадал, почему "оно опять сломалось".

Файлы zsh и порядок их загрузки
У zsh пять стартовых файлов в твоём домашнем каталоге, и читаются они не все сразу, а в зависимости от типа сессии. Тип определяется двумя независимыми флагами: login (вход в систему, ssh, или явный запуск как login shell) и interactive (есть человек за клавиатурой). Полный порядок для каждой сессии такой:
- ~/.zshenv - читается ВСЕГДА, при любом запуске zsh (даже когда скрипт дёргает оболочку неинтерактивно). Сюда кладут только переменные окружения, которые нужны абсолютно везде.
- ~/.zprofile - только в login-сессии, до .zshrc. Аналог .zlogin, но раньше. Сюда логично класть инициализацию PATH.
- ~/.zshrc - только в интерактивной сессии. Главный рабочий файл: алиасы, функции, опции, prompt, плагины, история.
- ~/.zlogin - только в login-сессии, после .zshrc. Для команд, что должны идти последними при логине.
- ~/.zlogout - при выходе из login-сессии.
Ключевой момент для macOS: Terminal.app и iTerm2 по умолчанию запускают КАЖДУЮ новую вкладку как login + interactive shell. Это особенность macOS - на Linux обычно наоборот. Поэтому у тебя при каждой вкладке читаются и .zprofile, и .zshrc. Проверь сам, чем является текущая сессия:
Код: Выделить всё
$ [[ -o login ]] && echo "login" || echo "non-login"
login
$ [[ -o interactive ]] && echo "interactive" || echo "non-interactive"
interactive
Код: Выделить всё
zsh ./script.shГде должен лежать PATH и почему Homebrew именно в .zprofile
Это место, где спотыкаются все. На Apple Silicon (M-серия) Homebrew ставится в /opt/homebrew - не в /usr/local, как было на Intel. Установщик в конце пишет тебе подсказку, и официальный способ подцепить brew в shell такой:
Код: Выделить всё
echo >> ~/.zprofile
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
Код: Выделить всё
brew shellenvКод: Выделить всё
$ /opt/homebrew/bin/brew shellenv
export HOMEBREW_PREFIX="/opt/homebrew";
export HOMEBREW_CELLAR="/opt/homebrew/Cellar";
export HOMEBREW_REPOSITORY="/opt/homebrew";
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}";
export MANPATH="/opt/homebrew/share/man${MANPATH+:$MANPATH}:";
export INFOPATH="/opt/homebrew/share/info:${INFOPATH:-}";
Код: Выделить всё
/opt/homebrew/binПроверка, кто победил в гонке за PATH:
Код: Выделить всё
$ which -a python3
/opt/homebrew/bin/python3
/usr/bin/python3
$ echo $PATH | tr ':' '\n' | head -3
/opt/homebrew/bin
/opt/homebrew/sbin
/usr/local/bin
Кастомизация .zshrc: prompt, история, автодополнение
Теперь основное мясо - zsh конфиг в ~/.zshrc. Начнём с prompt. В zsh за него отвечают переменные PROMPT (он же PS1, левая часть) и RPROMPT (правая, прижата к краю окна). Внутри - проценто-escape-последовательности:
Код: Выделить всё
%nКод: Выделить всё
%mКод: Выделить всё
%~Код: Выделить всё
%#Код: Выделить всё
%F{...}Код: Выделить всё
%fКод: Выделить всё
PROMPT='%F{cyan}%n%f@%F{green}%m%f %F{yellow}%~%f %# '
Код: Выделить всё
autoload -Uz vcs_info
precmd() { vcs_info }
zstyle ':vcs_info:git:*' formats '%F{magenta}(%b)%f'
setopt prompt_subst
RPROMPT='${vcs_info_msg_0_}'
Код: Выделить всё
${...}История. По умолчанию macOS пишет в ~/.zsh_history, но размер и поведение надо настроить руками, иначе теряешь команды:
Код: Выделить всё
HISTFILE=~/.zsh_history
HISTSIZE=50000
SAVEHIST=50000
setopt INC_APPEND_HISTORY # писать сразу, не при выходе
setopt SHARE_HISTORY # общая история между вкладками
setopt HIST_IGNORE_ALL_DUPS # выкидывать старые дубли
setopt HIST_IGNORE_SPACE # команда с пробелом в начале не пишется
setopt HIST_REDUCE_BLANKS # схлопывать лишние пробелы
Автодополнение. zsh-овский compsys - отдельная вселенная, но включается одной парой строк:
Код: Выделить всё
autoload -Uz compinit && compinit
zstyle ':completion:*' menu select # навигация стрелками по меню
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' # регистронезависимо
Код: Выделить всё
compinitКод: Выделить всё
brew install zsh-completionsКод: Выделить всё
if type brew &>/dev/null; then
FPATH="$(brew --prefix)/share/zsh-completions:$FPATH"
fi
autoload -Uz compinit && compinit
Алиас - простая замена слова, функция - когда нужны аргументы и логика. Не путай:
Код: Выделить всё
alias ll='ls -lhG' # -G это цвет в BSD ls, НЕ как в GNU (там --color)
alias gs='git status -sb'
alias ..='cd ..'
# функция: создать каталог и сразу зайти
mkcd() { mkdir -p "$1" && cd "$1"; }
Код: Выделить всё
sed -i ''Код: Выделить всё
brew install coreutils gnu-sedДва плагина, которые ставят почти все, и не зря:
- zsh-autosuggestions - дорисовывает серым продолжение команды из истории, принимаешь стрелкой вправо.
- zsh-syntax-highlighting - подсвечивает команду на лету: зелёным если бинарь найден, красным если опечатка. Видишь ошибку ДО Enter.
Код: Выделить всё
brew install zsh-autosuggestions zsh-syntax-highlighting
Код: Выделить всё
source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh
# syntax-highlighting ОБЯЗАТЕЛЬНО последней строкой .zshrc
source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
Фреймворки: oh my zsh, Prezto, starship, zinit
Можно собирать конфиг руками, а можно взять фреймворк. Самый известный - oh my zsh (он же oh-my-zsh, OMZ). Это менеджер плагинов и тем с огромной библиотекой. Ставится скриптом:
Код: Выделить всё
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
Плюс OMZ - быстрый старт и тонна готового. Минус - может заметно тормозить запуск shell (десятки плагинов = сотни миллисекунд на каждую вкладку). Альтернативы:
- Prezto - легче и быстрее OMZ, модульный, но менее популярен и хуже документирован.
- zinit - менеджер плагинов с ленивой загрузкой и turbo-режимом: грузит плагины асинхронно после показа prompt, старт почти мгновенный. Для тех, кому важна скорость и много плагинов.
- starship - это НЕ фреймворк, а только prompt, написанный на Rust, кросс-шелловый и очень быстрый. Ставится и подключается одной строкой в КОНЕЦ .zshrc:
Код: Выделить всё
brew install starship
Код: Выделить всё
eval "$(starship init zsh)"
Dotfiles в git: чтобы не настраивать заново
Один раз вылизал конфиг - не теряй его. Заведи репозиторий dotfiles. Простейший подход - папка ~/dotfiles с файлами и симлинками:
Код: Выделить всё
mkdir ~/dotfiles && mv ~/.zshrc ~/dotfiles/zshrc
ln -s ~/dotfiles/zshrc ~/.zshrc
cd ~/dotfiles && git init && git add . && git commit -m "init dotfiles"
Код: Выделить всё
[[ -f ~/.secrets ]] && source ~/.secretsТипичные грабли
- brew shellenv в .zshenv - PATH ломается после path_helper. Только .zprofile.
- syntax-highlighting не в конце файла - подсветка не видит алиасы или вовсе не работает.
- Забыл setopt prompt_subst - в prompt вместо ветки git голый текст с долларом.
- OMZ затёр .zshrc - ищи бэкап в ~/.zshrc.pre-oh-my-zsh, верни своё.
- Тяжёлый старт - замерь причину: . Если больше 0.3-0.4 с, виноваты плагины или compinit без кеша.
Код: Выделить всё
time zsh -i -c exit - Правки не применились - после редактирования сделай или открой новую вкладку. Старые сессии конфиг сами не перечитывают.
Код: Выделить всё
source ~/.zshrc - compinit ругается на insecure directories - у каталога в fpath слишком открытые права. Чинится .
Код: Выделить всё
chmod -R go-w "$(brew --prefix)/share"
- Определи тип текущей сессии: и то же для interactive.
Код: Выделить всё
[[ -o login ]] && echo login - Посмотри сырой вывод и убедись, что эта строка есть в ~/.zprofile (а не в .zshrc/.zshenv).
Код: Выделить всё
/opt/homebrew/bin/brew shellenv - В ~/.zshrc настрой PROMPT с подсветкой и блок истории с SHARE_HISTORY и HIST_IGNORE_ALL_DUPS. Открой две вкладки, выполни команду в одной - проверь стрелкой вверх в другой.
- Поставь , подключи source-ами (highlighting - последней строкой). Набери
Код: Выделить всё
brew install zsh-autosuggestions zsh-syntax-highlightingи убедись, что подсветка стала красной.Код: Выделить всё
gti status - Замерь старт: . Запиши число. Потом, если поставишь starship или OMZ - сравни.
Код: Выделить всё
time zsh -i -c exit - Заверни ~/.zshrc и ~/.zprofile в git-репозиторий dotfiles через симлинки.
- Какой единственный стартовый файл zsh читается при запуске неинтерактивного скрипта из launchd, и что из-за этого нельзя класть в .zshrc?
- Почему кладут в .zprofile, а не в .zshenv? Что делает path_helper и как он связан с порядком PATH?
Код: Выделить всё
eval "$(brew shellenv)" - Зачем zsh-syntax-highlighting подключать последней строкой .zshrc, и что сломается, если этого не сделать?
- Чем алиас отличается от функции, и почему на macOS работает, а
Код: Выделить всё
ls -G- нет?Код: Выделить всё
ls --color
zsh на macOS - это пять стартовых файлов с чётким порядком: .zshenv везде, .zprofile в login (сюда PATH и brew shellenv), .zshrc для интерактива (prompt, история, алиасы, плагины). Поняв этот порядок, ты перестаёшь воевать с PATH и Homebrew. Дальше дело техники: PROMPT/RPROMPT, нормальная история с дедупом, compinit, два плагина-подсказчика, и по вкусу - starship или oh my zsh. Всё это вынеси в git-dotfiles и переноси между машинами одной командой. Голый терминал превращается в инструмент, который работает на тебя.