Z shell (zsh)

28 августа 2026 · ~13 мин чтения

shell инструмент unix macos терминал

Z shell (zsh)

Zsh — это командная оболочка (shell) семейства Unix: программа, которая
читает то, что ты набираешь в терминале, разбирает это как язык, и
запускает нужные процессы. С 2019 года это shell по умолчанию во всех
новых macOS.

История

Что это такое

Shell (оболочка) — это программа, которая работает переводчиком между
человеком и операционной системой. Ты набираешь текст, shell разбирает
его как язык программирования с очень своеобразным синтаксисом,
запускает нужные процессы, соединяет их пайпами (труба, |), подставляет
переменные окружения, разворачивает шаблоны имён файлов (*.md),
записывает вывод в файл. Всё, что происходит в терминале, — это работа
shell.

Zsh — не единственный такой инструмент. Есть sh (POSIX shell —
общеотраслевой стандарт), bash (bourne-again shell, дефолт большинства
Linux), fish (friendly interactive shell, ориентирован на удобство),
dash (минималистичный, быстрый, часто как /bin/sh в Debian/Ubuntu).
Все они говорят на диалектах одного семейства языков и в большинстве
случаев умеют выполнять почти любой чужой скрипт.

Zsh выделяется тем, что он одновременно:

Легко перепутать zsh с oh-my-zsh или с темой powerlevel10k — но это
разные вещи. Zsh — сама оболочка. oh-my-zsh — сборник настроек и
плагинов поверх zsh. powerlevel10k — одна из тем визуального
оформления строки приглашения. Можно пользоваться zsh совсем без них.

Аналогии из жизни

Официант в ресторане. Ты не идёшь на кухню сам — ты называешь официанту,
что хочешь. Официант знает меню, переводит твои слова в заказ повару,
приносит блюда, а ещё запоминает твои предпочтения («как обычно, но без
лука»). Shell — точно такой же посредник между тобой и «кухней»
операционной системы.

Где ломается: официант ограничен меню и знанием ресторана. Shell же
может запустить вообще любую программу — ограничений «меню» нет, но зато
если программы нет, он молча скажет «command not found» и ничего умного
не подскажет.

Дирижёр оркестра. Оркестр — это отдельные программы (grep, awk,
sort, curl). Каждая умеет одну штуку. Дирижёр (shell) не играет сам,
но комбинирует их: сначала эта партия, потом та, а вот здесь труба (pipe)
передаёт звук из одной секции в другую. Красота Unix — именно в том, что
ты через shell собираешь сложный номер из простых инструментов.

Где ломается: у оркестра ноты синхронизированы. У shell — нет: если
одна программа в пайпе крашится, вторая может этого не заметить и
продолжить обрабатывать пустой поток. Отсюда легендарные баги
set -o pipefail в shell-скриптах.

Секретарь с блокнотом. У хорошего секретаря есть блокнот, куда он
записывает твои поручения, помнит контекст («вчерашний звонок Ивану —
это был по проекту N»), сам предлагает продолжения. Zsh с включённой
общей историей, автодополнением и подсказками — это тот же секретарь.

Где ломается: секретарь знает только то, что ты ему сказал. Shell тоже
не читает мысли — если ты не настроил zsh-autosuggestions, никаких
подсказок не будет. И общая история между окнами включается флагом,
дефолт — «каждое окно живёт своей историей».

Как это работает

Когда ты открываешь терминал и появляется приглашение (prompt) типа
~ $ — на самом деле там уже запущен процесс zsh, ждущий ввода. Что
происходит дальше по шагам:

1. Инициализация: файлы конфигурации. Zsh при запуске читает конфиги
в строгом порядке. Это важно — все грабли типа «переменная не
подхватывается» — обычно из-за путаницы в этих файлах:

Всегда, для любого запуска zsh:
  /etc/zshenv → ~/.zshenv

Только для login-shell (первый вход, ssh, консоль):
  /etc/zprofile → ~/.zprofile        (до .zshrc)

Только для интерактивной сессии (терминал):
  /etc/zshrc   → ~/.zshrc

Только для login-shell:
  /etc/zlogin  → ~/.zlogin           (после .zshrc)

При выходе из login-shell:
  /etc/zlogout → ~/.zlogout

Практически: переменные окружения (PATH, токены API) держи в
~/.zshenv — их подхватит любой запуск zsh, включая скрипты, запущенные
через launchd на Mac. Настройки интерактивности (алиасы, тема, история,
плагины) — в ~/.zshrc, чтобы они грузились только для реальных
сессий и не тормозили автоматические задачи.

2. Разбор строки: лексер и парсер. Когда ты нажимаешь Enter, zsh
берёт твою строку и разбирает её как язык. Простая команда
ls -la ~/dir | grep foo превращается во внутреннее дерево:

pipeline
├── command: ls
│     args: [-la, /Users/pavel/dir]  ← ~ развернуто в HOME
└── command: grep
      args: [foo]
      stdin: pipe от предыдущей команды

Между «строкой» и «деревом» происходит несколько преобразований:
подстановка переменных ($HOME), развёртывание тильды (~),
арифметика ($((2+2))), развёртывание фигурных скобок ({a,b,c}),
развёртывание шаблонов файлов (glob: *.txt, **/*.log), разбиение на
слова.

3. Word splitting — самая частая грабля. В bash по умолчанию
переменная в кавычках сохраняется как одна строка, а без кавычек —
разбивается на слова по пробелам. В zsh разбиения по словам без
кавычек НЕ происходит
— это одно из главных отличий от bash.

Пример боевой ошибки из моего вчерашнего лога:

SSH_КОМАНДА="ssh -J root@1.2.3.4 -p 1905"
ХОСТ="neiro@86.62.125.34"
$SSH_КОМАНДА "$ХОСТ"    # в zsh это НЕ ssh -J root@... -p 1905 neiro@...,
                         # а «попытка выполнить программу с именем
                         # 'ssh -J root@... -p 1905'» — крашится сразу.

Чтобы получить bash-подобное поведение, нужно писать ${=SSH_КОМАНДА}
явный опт-ин в word splitting. Либо использовать массив (правильный
способ): cmd=(ssh -J root@1.2.3.4 -p 1905); $cmd "$хост".

Это классический капкан для тех, кто переходит на Mac после Linux: копирует
скрипт, всё вроде такое же, но что-то ломается — а разгадка в этих 15
буквах.

4. Запуск процессов. Для каждой команды zsh делает системный вызов
fork() (создать копию процесса) и execve() (заменить в копии код на
код нужной программы). Пайп между командами — это на самом деле файловый
дескриптор ядра: stdout первой программы приколочен к stdin второй.

5. Автодополнение (compinit). Когда ты нажимаешь Tab, срабатывает
подсистема completion. Zsh загружает описания команд из
/usr/share/zsh/*/functions/Completion/ (тысячи файлов, по одному на
популярную утилиту), смотрит контекст твоего ввода (какое слово,
предыдущие аргументы) и подсказывает варианты. Это то, из-за чего zsh
знает, что после git checkout идёт имя ветки, а после docker exec
имя контейнера.

6. История и её магия. Zsh пишет каждую введённую команду в
~/.zsh_history. С правильно выставленными флагами (SHARE_HISTORY,
INC_APPEND_HISTORY) все окна терминала мгновенно видят историю друг
друга. Это одно из отличий от bash — там историю нужно пилить руками
через PROMPT_COMMAND.

Где встречается в обычной жизни

Где встречается в IT и бизнесе

Кто пользуется

Альтернативы и конкуренты

bash (Bourne-again shell)
- Плюс: дефолт всех Linux-серверов, значит скрипт с #!/bin/bash работает
везде без сюрпризов; знать bash обязательно, если работаешь с серверами.
- Минус: интерактивная часть слабее (тупое автодополнение, скудная
история), новые версии под GPLv3 — юридический геморрой для
проприетарного софта.

fish (Friendly Interactive Shell)
- Плюс: из коробки красивый, умный, с подсветкой синтаксиса и
подсказками — нулевая конфигурация даёт то, за что в zsh платишь часом
настройки.
- Минус: не POSIX-совместим. Скрипты, написанные для sh/bash, не
выполнятся напрямую. Синтаксис оригинальный. Меньше комьюнити,
меньше плагинов.

dash / ash
- Плюс: маленький, быстрый; часто это /bin/sh в Debian/Ubuntu (для
системных скриптов).
- Минус: минимализм — почти ничего сверх POSIX; интерактивно неудобен,
никто в нём не сидит.

PowerShell (Microsoft, кроссплатформенный)
- Плюс: работает с объектами, а не с текстом (пайпы передают структурированные
данные — намного мощнее для обработки JSON, XML, .NET-объектов); есть
на Windows/macOS/Linux.
- Минус: чужеродный синтаксис для Unix-мира, тяжеловесен, стартует
медленнее zsh на секунду-две.

nushell (nu)
- Плюс: молодой (2019+), тоже объектный shell, крутая работа с CSV/JSON
из коробки. Красивые таблицы.
- Минус: пре-1.0, ломающие изменения между версиями, ещё не мейнстрим,
мало готовых сниппетов в интернете.

Когда НЕ стоит использовать

Связанные понятия

Литература и источники

Где встретилось у меня

Вчера настраивал HTTPS для нового домена на VPS через certbot, и
скрипт публикации publish_neiro3.sh упал на конструкции
$SSH_КОМАНДА "$ХОСТ" — потому что zsh, в отличие от bash, не
разбивает переменную на слова без явного ${=var}. Отдельно — про
кириллические имена переменных: zsh их принимает нормально, а bash — нет.
Оба случая — мелкие особенности shell, которые бьют по времени именно
тогда, когда о них забываешь.

Краткое резюме