Terminal vs Shell: что есть что
Открыть «терминал» и «запустить shell» — это два разных действия. Но в обыденной речи их путают: «терминал не работает», «открой bash в терминале», «зайди в shell». Junior должен чётко понимать разницу — это пригодится, когда что-то странное происходит (например, цвета не отображаются, или нажатие стрелки выдаёт ^[[A, а не историю команд).
В этом уроке разберём цепочку: эмулятор терминала -> tty -> shell -> программа. И поймём, как они между собой взаимодействуют.
Истоки: телетайп (teletype, tty)
В 1960-х компьютеры были редкими и дорогими, а пользователей много. Чтобы все могли работать с одним компьютером, к нему подключали электромеханические устройства — телетайпы. Это была печатная машинка с клавиатурой: вы печатаете на клавишах, символы по проводу уходят в компьютер; компьютер шлёт обратно текст — он распечатывается на бумаге.
Сокращение — tty (TeleTYpe). Это сокращение прижилось и живёт до сих пор. Каждый раз, когда вы видите /dev/tty, pts/0, man tty — это исторический след телетайпов 60-х.
Идея осталась та же, что и в 1960-х: устройство для ввода/вывода текста, подключённое к компьютеру. Только теперь «устройство» — это окно на экране, а «провод» — это виртуальный (pseudo-terminal, pty).
Что такое эмулятор терминала
Эмулятор терминала (terminal emulator) — это программа, которая показывает текст в окне и передаёт клавиатурный ввод дальше. Это GUI-программа, как Chrome или Excel.
Примеры эмуляторов:
Эмулятор НЕ выполняет команды. Он только:
- Показывает текст в окне.
- Передаёт нажатия клавиш дальше (в shell).
- Эмулирует ANSI escape codes (цвета, позиционирование курсора, очистка экрана).
Когда вы открываете эмулятор, он автоматически запускает shell внутри — обычно тот, который указан в настройках или в /etc/passwd. На macOS по умолчанию zsh (с Catalina 2019), на Linux — обычно bash.
Что такое shell
Shell — это программа-интерпретатор, которая читает команды и выполняет их. На каждое нажатие Enter shell делает: разбирает строку, ищет команду по $PATH, fork+exec, ждёт завершения, показывает prompt снова.
Это абсолютно отдельная программа от эмулятора. Эмулятор может работать без shell (внутри запустится что угодно). Shell может работать без эмулятора (например, в скрипте, в SSH-сессии, в systemd-сервисе).
fork() и exec() — как ядро запускает процессЦепочка такая: эмулятор создаёт pty (pseudo-terminal pair), запускает shell, подключает его stdin/stdout/stderr к pty. Когда вы вводите команду в окне эмулятора, символы идут через pty в shell. Когда shell пишет вывод — он идёт через pty обратно и эмулятор рисует это в окне.
/dev/tty и pts/N
Откройте терминал и выполните tty:
$ tty
/dev/pts/0
Это говорит: ваш shell привязан к pseudo-terminal slave с номером 0. На Linux pseudo-terminals живут в /dev/pts/. Откройте ещё один терминал — там будет /dev/pts/1. Третий — pts/2. И так далее.
На macOS пути немного отличаются: /dev/ttys000, /dev/ttys001 и так далее. Идея та же.
/dev/tty — это специальный «магический» путь, который всегда указывает на текущий терминал процесса:
$ echo "Hello" > /dev/tty
Hello
$ cat /dev/tty # читает с клавиатуры
type something
type something
Это полезно, когда программа хочет общаться с пользователем напрямую, в обход stdin/stdout (например, при чтении пароля).
В скрипте может быть полезно знать, запущен ли он в терминале или в pipe. Проверка: [[ -t 0 ]] — если stdin привязан к терминалу. Это позволит скрипту вести себя по-разному: интерактивно или нет.
Какие есть shell-ы
На современных системах живут несколько shell-ов. Каждый — своя программа, со своим синтаксисом, своим набором фич.
В чём практическая разница? Чтобы понять, посмотрим, как один и тот же скрипт ведёт себя в разных shell:
# bash / zsh / sh — POSIX-совместимый синтаксис
for i in 1 2 3; do
echo "Number: $i"
done
# fish — другой синтаксис
for i in 1 2 3
echo "Number: $i"
end
# nushell — ещё другой
for i in [1 2 3] {
echo $"Number: ($i)"
}
Bash и zsh имеют общий синтаксис скриптов (с минимальными различиями). Fish и nushell — несовместимы.
Какой shell у вас сейчас:
$ echo $SHELL
/bin/bash
$ readlink /proc/$$/exe # Linux: реальный путь к процессу shell
/usr/bin/bash
$SHELL — это переменная окружения, которая хранит «предпочитаемый shell пользователя» (из /etc/passwd). Это не всегда тот shell, в котором вы СЕЙЧАС находитесь — если вы запустили zsh из bash, в этом zsh переменная $SHELL всё ещё будет показывать /bin/bash.
macOS vs Linux: дефолтный shell
Историческая нота. До macOS Catalina (2019) дефолтным shell был bash. Apple перешла на zsh из-за лицензии: bash перешёл на GPLv3, а Apple GPLv3 -несовместимы (по корпоративным юридическим причинам). zsh с лицензией MIT — никаких проблем.
На Linux дефолт — bash почти везде, за исключением Alpine (там ash из busybox) и некоторых других минимальных дистрибутивов.
Что это значит на практике:
- На Mac:
bashещё есть, но это старый bash 3.2 (последний GPLv2). Чтобы получить bash 5.3 —brew install bash. - На Ubuntu/Debian: bash 5.3 из коробки в новых релизах (26.04 / Trixie). На старых — bash 5.0+ — тоже хорошо.
- В Docker-образах Alpine: НЕТ bash по умолчанию, только ash. Если делаете
RUN bash script.sh— упадёт. Решение:apk add bashв Dockerfile, или писать на чистом POSIX sh.
# Узнать версию bash
$ bash --version
GNU bash, version 5.3.0(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2025 Free Software Foundation, Inc.
# На macOS встроенный — старый
$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin22)
Старый bash 3.2 на macOS отсутствуют важные фичи (ассоц-массивы, &>, и многое другое из bash 4.0+). Поэтому на Mac для bash-скриптов обычно ставят свежий bash через Homebrew и shebang делают #!/usr/bin/env bash (берёт из PATH, не системный /bin/bash).
WSL2 на Windows
Windows Subsystem for Linux 2 — это полноценное Linux-ядро, работающее на Windows через Hyper-V. На WSL2 можно поставить Ubuntu, Debian, Alpine — почти любой дистрибутив. Поверх — bash, как обычно.
Для DE на Windows-машине это лучший вариант. Не виртуалка с лагающим GUI, не curio cygwin — настоящий Linux под капотом. Файлы из Windows доступны через /mnt/c/Users/..., файлы из Linux — через \\wsl.localhost\Ubuntu\home\user\....
# В PowerShell на Windows
PS> wsl --install -d Ubuntu
PS> wsl
user@host:~$ uname -a
Linux host 6.6.0-microsoft-standard-WSL2 #1 SMP ...
После этого внутри WSL — обычный Linux, можно следовать любым Ubuntu-инструкциям.
Цепочка целиком
Когда вы открываете терминал и пишете команду, происходит следующее:
Это много шагов, но всё работает на скорости миллисекунд. Для пользователя выглядит как «нажал Enter — увидел вывод».
Когда что-то идёт не так:
- Стрелки выводят
^[[A— эмулятор не правильно эмулирует, или shell не понимает ANSI escape codes для стрелок (старый dash, например). - Цвета не работают —
TERMпеременная не та (echo $TERMдолжен показатьxterm-256colorили похожее). - Размер экрана 80x24, а окно больше — терминал не передал ему обновление размера (
stty -aпокажет текущий размер).
Попробуй сам
Откройте 2-3 терминала и посмотрите, что у вас:
$ tty
/dev/pts/0 # или /dev/ttys000 на Mac
$ echo $SHELL
/bin/bash # или /bin/zsh на Mac
$ ps -p $$ # показать инфу о текущем процессе shell
PID TTY TIME CMD
12345 pts/0 00:00:00 bash
$ echo $TERM
xterm-256color # на современных эмуляторах
$$ — это специальная bash-переменная: PID текущего shell. ps -p $$ показывает информацию о нём самом.
Запустите вложенный shell:
$ bash # запустили bash внутри bash
$ ps -p $$
PID TTY TIME CMD
12346 pts/0 00:00:00 bash
$ exit # вышли из вложенного
$ ps -p $$
PID TTY TIME CMD
12345 pts/0 00:00:00 bash
Видите: вложенный shell имеет свой PID, и Ctrl+D / exit возвращает в родительский.