В терминале всего 33 Ctrl-шортката — разбор от Julia Evans
В терминале можно зажать Ctrl только в 33 комбинациях — остальные пропускаются или становятся ANSI escape. Julia Evans разбирает, где живёт каждый код и почему Ctrl-M = Enter.
Если вы когда-нибудь нажимали Ctrl-1 в терминале и удивлялись, почему ничего не происходит — это не баг, а ограничение ASCII: только 33 комбинации с Ctrl дают различимый control-код, остальные терминал пропускает как обычный ввод или превращает в ANSI escape-последовательность. Понимание этой таблицы помогает в тех самых моментах, когда терминал «сломан», Ctrl-S внезапно всё заморозил, а Ctrl-W в одной программе удаляет слово, а в другой — закрывает окно. Julia Evans, автор блога jvns.ca и иллюстрированных гайдов (zines) про Linux и командную строку, объяснила, почему Ctrl-M — это Enter, а Ctrl-S замораживает терминал. Ниже — перевод её разбора.
Недавно я много думала про терминал, и вчера мне стало интересно: что вообще происходит со всеми этими «control codes» — Ctrl-A, Ctrl-C, Ctrl-W и так далее? Я собрала таблицу всех 33 ASCII control characters и того, что они делают на моей машине (macOS). Оговорок там миллион, но ниже я расскажу, что это всё значит и какие есть нюансы.
Ключевые выводы
Что нужно знать про ASCII control characters в терминале
Коротко о главном из разбора
- В ASCII всего 33 control-кода: A–Z (26 штук) плюс 7 дополнительных (@, [, \, ], ^, _, ?). Сделать
Ctrl-1как шорткат невозможно. - Коды обрабатываются в трёх разных местах: OS terminal driver (например, Ctrl-C → SIGINT), библиотека readline (Ctrl-A, Ctrl-E) или само приложение (emacs использует Ctrl-X).
Ctrl-M= Enter,Ctrl-I= Tab — поэтому их нельзя использовать как отдельные шорткаты без спец-настройки эмулятора.- Режим терминала — canonical или noncanonical — определяет, кто обрабатывает Backspace, Ctrl-W и Ctrl-U: OS или сама программа. Разбор обоих режимов — в теле статьи.
- Утилита
stty -aпоказывает текущие маппинги OS-кодов, аstty saneлечит «сломанный» терминал. - Backspace на разных системах посылает либо байт 127, либо 8 — отсюда исторические войны и конфигурации через
stty erase.
Коды обрабатываются в разных местах
Первое, что меня удивило: 33 control-кода делятся (очень условно) на три категории в зависимости от того, где они обрабатываются.
- Обрабатываются OS terminal driver — например, когда ОС видит байт 3 (
Ctrl-C), она посылает сигнал SIGINT текущей программе. - Передаются приложению как есть, и приложение делает с ними что хочет. Внутри этой группы ещё три подгруппы: соответствуют реальной клавише (
Enter→ байт 13,Tab,Backspace); используются библиотекой readline для редактирования строки (Ctrl-A,Ctrl-E,Ctrl-W); используются конкретными приложениями (Ctrl-Xв emacs).
Никакой логики в том, какой код к какой категории относится, нет — просто так исторически сложилось.
Почему именно 33 — и почему «Ctrl-1» не существует
Ещё один сюрприз: control-кодов всего 33 штуки — буквы A–Z плюс семь дополнительных символов (@, [, \, ], ^, _, ?). Это значит, что если вы хотите сделать Ctrl-1 шорткатом в терминальном приложении — такого шортката просто не существует. На моей машине Ctrl-1 — это ровно то же самое, что нажать 1, а Ctrl-3 эквивалентно Ctrl-[.
Ctrl+Shift+C тоже не control-код — это комбинация, которую обрабатывает сам эмулятор терминала. В Linux Ctrl+Shift+X часто используется эмулятором для копирования, открытия новой вкладки или вставки — до TTY они не доходят.
Я всё время использую Ctrl+Left Arrow, но это тоже не control-код — это ANSI escape sequence (ESC[1;5D, где ESC — это сам байт 27 = Ctrl-[), совсем другая история.
Это устроено совсем не так, как шорткаты в GUI, где можно сделать Ctrl+любая клавиша.
Официальные ASCII-имена бесполезны
Каждый из 33 control-кодов имеет имя в ASCII (например, байт 3 — это ETX). Но эти имена придумывали не для компьютеров, а для телеграфа. Когда коды перешли в UNIX-терминалы, половина из них сменила смысл. В итоге ASCII-имена сегодня бесполезны в 50% случаев — проще вообще игнорировать их, чем пытаться понять, какие имена ещё соответствуют оригинальному значению.
Почему Ctrl-M и Ctrl-I — это Enter и Tab
Ctrl-M буквально идентичен Enter, а Ctrl-I — это Tab. Это делает их почти непригодными для шорткатов.
Некоторые всё равно используют Ctrl-I и Ctrl-M как шорткаты, но для этого надо настроить эмулятор терминала, чтобы он обрабатывал эти нажатия иначе, чем по умолчанию. Основной вывод: если пишете терминальное приложение — не используйте Ctrl-I и Ctrl-M как шорткаты.
Как узнать, какой именно код посылается
Пока я писала этот разбор, мне пришлось много экспериментировать с разными комбинациями клавиш. Я написала маленький Python-скрипт echo-key.py, который распечатывает получаемые байты. Наверняка есть более официальный способ, но мне удобнее иметь скрипт, который можно подкрутить под себя.
Оговорка: canonical vs noncanonical mode
Два кода — Ctrl-W и Ctrl-U — я в таблице пометила как «обрабатываются OS». На самом деле это не всегда так — зависит от режима терминала.
В canonical mode (построчный ввод) программа получает ввод только после нажатия Enter — до этого OS сама обрабатывает Backspace, Ctrl-W и другие правки. В noncanonical mode (посимвольный ввод) программа получает каждое нажатие сразу, и коды Ctrl-W и Ctrl-U проходят в программу, которая обрабатывает их как хочет.
Примеры программ в canonical mode:
- Любые неинтерактивные утилиты вроде
grepилиcat. git, по моему опыту.
В noncanonical mode:
python3,irbи другие REPL.- Ваш шелл.
- Любой полноэкранный TUI (
less,vim).
Оговорка: все OS-коды настраиваются через stty
Я написала «Ctrl-C посылает SIGINT», но технически это не обязательно так. Все коды, которые обрабатывает OS terminal driver, плюс Backspace, можно переназначить утилитой stty. Текущие маппинги смотрят через stty -a.
Я лично ни разу ничего не переопределяла через stty и не могу придумать, зачем бы мне это делать — это рецепт для путаницы и катастрофы. Но когда я спросила в Mastodon, люди назвали такие сценарии:
- Починить сломанный терминал через
stty sane. - Настроить работу Backspace:
stty erase ^H. - Включить поток управления:
stty ixoff. - Некоторые даже переназначают SIGINT на другую клавишу, например на DELETE.
Оговорка: сигналы можно отключить
- Если режим терминала
ISIG(флаг, разрешающий сигналы от Ctrl-C и Ctrl-Z) выключен, OS вообще не посылает сигналы. Vim, например, отключает ISIG при запуске. - На macOS и других BSD-системах есть дополнительный control-код
Ctrl-T, который посылает SIGINFO.
Какие режимы ставит программа, можно посмотреть через strace: режимы терминала устанавливаются системным вызовом ioctl.
Комбинация режимов, которую ставит vim при запуске, по сути и есть так называемый «raw mode» — подробности в man cfmakeraw.
Конфликтов очень много
Раз кодов всего 33, конфликтов за один и тот же код — навалом. По умолчанию Ctrl-S замораживает экран, но если выключить этот flow-control, readline использует Ctrl-S для forward search.
Другой пример: Ctrl-T на моей машине то посылает SIGINFO, то переставляет местами два последних символа, то делает что-то совсем третье — в зависимости от того, установлен ли в программе ISIG и использует ли она readline (или имитирует его поведение).
Backspace, «другой» Backspace и историческая боль
В таблице я пометила байт 127 как «backspace», а байт 8 как «другой backspace». История оказалась настолько запутанной, что именно этот пункт собрал больше всего откликов в Mastodon.
Вот как это работает на моей машине:
- Я нажимаю клавишу
Backspace. - В TTY уходит байт 127, в ASCII он называется DEL.
- OS terminal driver и readline оба замапили 127 на «удалить символ» — работает и в canonical, и в noncanonical mode.
- Предыдущий символ исчезает.
Если нажать Ctrl+H, в readline-приложении это сработает как Backspace, а в программе без readline (например, cat) просто напечатается ^H.
У кого-то шаг 2 другой: клавиша Backspace посылает байт 8 вместо 127, и чтобы она работала, надо настроить OS через stty erase ^H. В Debian Policy Manual есть целый раздел про конфигурацию клавиатуры — по моему пониманию, его написали в 90-х, когда царила путаница с тем, что должен делать Backspace, и нужен был какой-то стандарт, чтобы всё заработало.
На разных машинах всё по-разному
Я почти наверняка упустила ещё десяток способов, которыми «как это работает на моей машине» может отличаться от «как это работает у других», и в моих описаниях тоже могут быть неточности. По stty -a у меня есть ещё три экзотических маппинга — Ctrl-O как «discard», Ctrl-R как «reprint» и Ctrl-Y как «dsusp» — но на практике они редко что-то делают и чаще всего проходят в приложение как есть.
Честно говоря, это необязательно знать
Мне кажется, содержимое этого поста интересное, но не обязательно полезное. Я использовала терминал каждый день последние 20 лет, не зная почти ничего из этого — я просто знала на практике, что делают Ctrl-C, Ctrl-D, Ctrl-Z, Ctrl-R, Ctrl-L (ну и Ctrl-A, Ctrl-E, Ctrl-W), и не задумывалась о деталях. Этого почти всегда хватало, кроме случая, когда я возилась с xterm.js.
От редакции: знание это становится практическим в одном-двух сценариях, и их стоит держать в голове. Во-первых, когда терминал «ломается» — ввод не виден, Enter работает странно — вместо перезапуска шелла поможет stty sane или reset: теперь понятно, что именно они восстанавливают. Во-вторых, Ctrl-S, «замораживающий» терминал, — не баг, а реликт аппаратного flow control: отключить его навсегда можно через stty -ixon в .bashrc / .zshrc. Остальное — любопытная инженерная археология.
FAQ
Почему именно 33 кода, а не 32 или 128?
ASCII control characters занимают позиции 0–31 плюс 127 — это 33 байта с диапазоном ≤ 31 или = 127. Исторически старшие биты использовались для печатных символов, а диапазон 0–31 был зарезервирован под управление периферией телеграфа.
Что делает Ctrl-C на самом деле?
Когда терминал в canonical mode с включённым ISIG и дефолтными маппингами, нажатие Ctrl-C посылает в TTY байт 3. OS terminal driver перехватывает этот байт и отправляет сигнал SIGINT всей foreground process group — группе процессов, связанной с текущим TTY. Обычно это просто одна запущенная программа, но если запущен pipeline (a | b | c) — сигнал получат все процессы в нём.
Как отличить canonical от noncanonical mode?
Если программа ждёт нажатия Enter перед тем, как получить ваш ввод (cat без аргументов, grep) — canonical. Если реагирует сразу на каждую клавишу (vim, less, REPL) — noncanonical. Проверить точно можно через strace -e ioctl или чтение через stty -a.
Что делать, если терминал «сломался»?
Наберите вслепую stty sane и нажмите Enter — команда сбросит все настройки терминала в дефолтные. Если не помогло — reset (отрисовывает заново) или закрыть сессию и открыть новую. Подборку других удобных трюков мы собрали в материале «Трюки в терминале, которые реально экономят время (и нервы)».
Можно ли в терминальном приложении использовать Ctrl+цифра?
Нет, если только вы не просите пользователя перенастроить эмулятор терминала отдельно. Большинство эмуляторов (xterm, Alacritty, iTerm2, Windows Terminal) отправляют обычный код цифры вне зависимости от Ctrl. В GUI можно — там шорткаты обрабатывает оконный менеджер, а не TTY.
Итог
ASCII control characters — это слоёный пирог из трёх эпох: телеграфные корни 60-х, POSIX-терминалы 80-х и GUI-эмуляторы 2020-х, которые поверх всего этого ещё накручивают свою логику. Когда Ctrl-S внезапно замораживает экран, а Ctrl-T делает разные вещи в разных программах — это не баг, а побочный эффект 60 лет совместимости.
Оригинал: jvns.ca — ASCII control characters in my terminal. Также читайте на tproger: 7 новых TUI-инструментов для терминала и 15 полезных команд терминала macOS.