Консоль 21 века: mosh, tmux, fish
В своей работе мне приходится проводить чуть ли не все свое время в консоли, как в локальной, так и в удаленной. Нет, что вы, я не жалуюсь, даже наоборот — мне нравятся возможности автоматизации, которые предоставляют консольные инструменты, и работа в консоли вполне продуктивна.
Но если вы проводите за своим инструментом до 80% рабочего времени, то желательно убедиться, что вы не тратите время впустую и что работа доставляет вам удовольствие. В этой статье я бы хотел немного рассказать про те инструменты, которыми я лично пользуюсь каждый день, и про то, как они улучшают user experience (и, часто, продуктивность) при работе с консолью и с удаленными серверами в частности.
Проблемы ssh
При работе с удаленными серверами по ssh есть много вещей, которые могут фрустрировать, но основных проблем две, и первая из них принципиально неразрешима в рамках ssh:
- При высоком round-trip latency (>100 ms) пользовательский ввод появляется с ощутимой задержкой, а при использовании мобильного интернета с edge (latency 1000 ms) работа становится подобна пытке
- При временных проблемах (несколько минут) с доставкой пакетов, соединение может порваться с write failed: broken pipe, причем узнаете вы об этом только при попытке ввода или при использовании настроек вроде keepaliveinterval
Первая проблема неразрешима потому, что ssh by-design является просто транспортом для байтов, и существующие приложения на это поведение расчитывают. Поскольку ssh не пытается интерпетировать этот поток байтов, он не может осуществлять предиктивный ввод. Лично для меня именно эта проблема наиболее актуальна, поскольку мне приходится работать с серверами в европе и США, и во втором случае задержка составляет около 200 мс и является принципиально неустранимой, по крайней мере до изобретения квантовой коммуникации или чего-нибудь подобного. Вторая же проблема проявляется в наших условиях относительно редко, но всё же неприятно переустанавливать все соединения при сбоях сети (и перезапускать упавшие приложения, если они почему не были запущены в screen).
Решение — mosh (MObile SHell)
Решение указанных выше проблем весьма радикально — mosh ( mosh.mit.edu ) представляет из себя отдельную клиент-серверную систему, работающую по UDP и посылающую диффы экрана вместо подхода, используемого SSH, который передает байты «как есть». Изначальное соединение происходит по ssh, но ssh лишь запускает mosh-server и выходит после этого. Из-за этого подключение к серверу с использованием mosh происходит немного дольше, чем просто по SSH.
Для того, чтобы работать через mosh вместо ssh, нужно установить mosh-клиент к себе на компьютер и mosh-server на удаленный хост. В целом, не обязательно его устанавливать общесистемно — демон работает из-под пользователя, и при соединении можно указать бинарник сервера (пример: mosh --server /home/yuriy/bin/mosh-server my-server-hostname).
Поскольку mosh посылает диффы экрана по udp, он очень сильно отличается по своим свойствам от привычного ssh через tcp/ip:
Соединение никогда не рвется
Нет никакого «соединения». При восстановлении связности сети вы снова начинаете видеть текущее состояние консоли. Вы можете также менять способ подключения к серверу, например поменять wi-fi точку доступа, и все продолжит работать. Это особенно удобно при использовании VPN через мобильный интернет, который представляет из себя образец нестабильности.
Забудьте про возможность скроллить историю колесом мыши
Локальная прокрутка не будет работать, так как mosh рисует все в альтернативном экране, и показывает только разницу с предыдущим состоянием, не пересылая остальное. Для того, чтобы история сохранялась хоть куда-нибудь, необходимо использовать screen или tmux, о котором будет рассказано далее. С некоторым напильником все же можно заставить колесо мыши работать, но ощущения все равно будут «не те».
При высокой latency включается предиктивное echo
Если вы, к примеру, используете SSH через EDGE, то я вам очень не завидую :). В этом случае mosh умеет «понимать», в каком контексте он сейчас работает и в большинстве случаев способен адекватно отображать ваш ввод еще до того, как хоть что-то придет с сервера. На иллюстрации ниже «подчеркнутый» текст — это текст, введенный пользователем, но эхо (в большинстве случаев — просто введенный пользователем текст) с сервера еще не получено. Также, даже в случае не слишком высокой latency (например, 50мс, уже заметные на глаз), mosh старается показать пользовательский ввод мгновенно, если «уверен», что в данный момент с сервера просто возвращается введенный текст. Таким образом, в случае latency порядка 50-100 мс, работа в удаленной консоли становится практически неотличимой от локальной. За исключением возможности увидеть историю, как было упомянуто выше.
Все указанные вещи достигаются за счет того, что сервер тоже «рендерит» вывод из консоли и держит текущее состояние экрана у себя в памяти. В трекере mosh'а есть предложения уметь хранить еще и историю, чтобы можно было отказаться от дополнительной прослойки в виде screen/tmux, но пока что авторы ничего не сделали в этом направлении.
Давайте теперь перейдем к тому, что такое tmux и чем он лучше screen:
Проблемы screen
К сожалению, я не большой знаток screen, поэтому из проблем я бы мог назвать только две — отсутствие поддержки разделения экрана по вертикали (вместо горизонтального по умолчанию) и медленное развитие в целом из-за очень старой кодовой базы. Удобство использования у screen тоже оставляло желать лучшего. Поддержку разделения по вертикали запилили в новой версии screen 4.2, но к тому моменту я лично уже перестал им пользоваться. В целом, screen по-прежнему является стандартом де-факто, как и ssh, и нельзя его списывать со счетов.
Что такое tmux и его преимущества перед screen
Если вы никогда не слышали о терминальных мультиплексорах (Terminal MUltipleXor), то предыдущий абзац вам вряд ли будет понятен. Поэтому, расскажу немного про то, что это такое:
Представьте себе ситуацию, что вы хотите запустить какую-то длительную операцию по ssh, и у вас отваливается соединение. Или вы не хотите ждать ее завершения, потому что эта команда представляет из себя «while true; do run-daemon; done». По умолчанию, интерактивные сессии посылают сигнал SIGHUP всем процессам из этой сессии при отключении, и процессы завершаются.
Эту проблему можно решать по-разному, например использовать команду nohup, которая перенаправляет вывод в файлы и игнорирует SIGHUP. Но более интересным решением является использование терминальных мультиплексоров, например screen или tmux. При первом запуске обычно стартует новая сессия, в которой вы можете работать, и эта сессия не привязана жестко к вашему ssh-соединению. Вы можете отключиться, например закрыв вкладку с ssh-подключением, или нажав «Ctrl+B D» (то есть, сначала нажать Ctrl+B, а затем лтпустить Ctrl и нажать D), и все запущенные внутри tmux приложения останутся нетронутыми. Вы можете потом подключиться к этой сессии обратно, введя «tmux attach» или screen с кучей флажков, например "-rD".
В целом, tmux выглядит более user-friendly и поддерживает из коробки довольно интересные вещи:
- Вышеупомянутое разделение экрана по вертикали, например с помощью Ctrl+B :split-vertical. К сожалению, новую версию screen с поддержкой такого разделения экрана все еще можно встретить не везде.
- Возможность подключиться к существующей сессии несколько раз — tmux attach подключит вас к существующей сессии, не «выкидывая» других подключенных пользователей
- Перемотка истории с помощью колеса мыши заданием опции mouse-mode on
- Отсутствие необходимости запускать «script» при попытке подключиться к screen другого пользователя через sudo
Скорее всего, все указанные выше вещи можно сделать и в рамках screen, просто tmux изначально спроектирован более простым для пользователя и имеет больше фич. Если вы вдруг пользуетесь iterm2, то в нем есть встроенная поддержка интеграции с tmux, что тоже может быть аргументом в его пользу.
Ну и напоследок поговорим про fish — friendly interactive shell
Недостатки bash
Честно говоря, сложно сказать, какие у баша достоинства. Самое большое достоинство баша состоит в том, что это самый распространенный шелл и что он стоит по умолчанию в большинстве дистрибутивах линукса, mac os x и даже cygwin. Также, bash является posix-совместимым, что означает, что можно заменить /bin/sh на /bin/bash в системе и «ничего не сломается». Однако интерактивные возможности баша находятся далеко позади другого распространенного конкурента в лице zsh, в баше даже нет правого prompt'а. Большинство возможностей как в bash, так и в zsh, выключены по умолчанию. Чтобы воспользоваться всеми фичами этих шеллов, нужно либо потратить значительное количество времени на их настройку, либо использовать готовые решения, например oh-my-zsh.
Интерактивный шелл 21 века — fish
Достаточно один раз увидеть, как работает fish (friendly interactive shell), и вы уже не захотите пользоваться ничем другим :). В целом, fish — это именно интерактивный шелл, не POSIX-совместимый. То есть, скрипты, написанные для /bin/sh или /bin/bash с его помощью выполнять нельзя. Синтаксис шелла немного отличается от обычного, см. примеры ниже.
Основные фичи:
- Авто-дополнение для команд из истории и для имен файлов прямо по мере ввода, в режиме реального времени
- Поставляется «с батарейками» — из коробки есть умное авто-дополнение для большинства команд, есть git_prompt и прочее
- Наличие левого и правого prompt'ов, правильное вычисление их размеров, prompt задается функцией, а не строкой из PS1
- Автоматическая история для директорий — Alt+Shift+стрелки налево-направо позволяют прозрачно возвращаться к предыдущей директории и обратно
- «Исправленный» синтаксис, никаких больше do, then, fi, esac, $?, бэктиков (`cmd`) и множества других вещей — с адекватными подсказками — это делает fish не-POSIX шеллом, что, в общем-то, достоинством не является
- Поддержка аббревиатур — к примеру, создав аббревиатуру «st -> git status», вы будете «вводить» «git status», как только ввели «st» — таким образом будет работать авто-дополнение и окружающие люди не будут пугаться ваших непонятных сокращений
- Улучшенное авто-дополнение по <tab> — показываются не только сами подсказки, но и их тип (например, для git checkout показываются отдельно ветки и теги )
Есть также просто мелкие приятные улучшения:
- prompt отображается нормально и не портится, даже если вывод программы не оканчивается переводом строки
- подчёркивание правильных путей до файлов
- показ красным некорректных команд — вы можете понять, что вы ввели неправильное имя команды, не нажимая Enter
- возможность легко перенаправить stderr в pipe с помощью синтаксиса вида 2>| или ^|
- все есть функция, нет алиасов, и для функций поддерживается autoload — при загрузке fish не считывает все доступные функции в память, а также позволяет менять код функций «на лету» без необходимости перечитывания полного конфига
- локальная переменная CMD_DURATION — время исполнения последней команды, в миллисекундах
- сокращенный путь до текущей директории по умолчанию — берутся первые буквы каждого из компонентов пути, кроме последней директории (например, /usr/local/bin/ -> /u/l/bin/)
Пример правого prompt'а с использованием CMD_DURATION и git_prompt:
$ cat ~/.config/fish/functions/fish_right_prompt.fish
function fish_right_prompt
if test $status = 0
echo -n (set_color green)
else
echo -n (set_color red)
end
echo $CMD_DURATION (set_color normal)ms (set_color yellow) (__fish_git_prompt "%s") (set_color normal)
end
Обратите внимание на синтаксис if-выражений — он больше похож на python, чем на shell. Выражения в скобках означает «вставить результат выполнения команды» — в bash это записывается как $(...), в fish первый символ доллара не требуется.
Вот, как это выглядит:
В правом приглашении показывается время выполнения команды в миллисекундах, зеленым цветом, если команда выполнена успешно, и красным, если произошла ошибка. Желтым цветом показывается текущая ветка, если есть.
Интеграция mosh, tmux и fish, выводы
При использовании tmux и fish вместе, возможны 2 проблемы, обе которых немного неприятны, но легко решаемы:
Вывод из stderr «пропадает» — github.com/fish-shell/fish-shell/issues/2115 — решается запуском «fish 2>&1» вместо просто «fish»
По умолчанию tmux ставит переменную окружения $TERM в screen, хотя и поддерживает 256-цветный режим, поэтому нужно выставить переменную окружения «export TERM=screen-256color», чтобы получить обычный fish вместо fallback'а на 16-цветный режим
Также, при использовании правого prompt'а в fish, а также при разделении окон по вертикали в tmux, клиент для mosh может начать сдвигать правую границу при быстром вводе, что приводит к временным артефактам при рисовании. Это происходит из-за того, что mosh не понимает, что положение рамок, разделяющих панели в tmux, а также положение правого prompt'а в fish фиксировано, и сдвигает их при вводе направо, вместе с остальным текстом.
Итого: все перечисленные выше инструменты весьма новые, и пока что не отточены до такого же состояния, как обычная связка ssh+screen+bash, но со временем ситуация улучшается, разработчики учитывают feedback от пользователей и улучшают интеграцию с другими «новыми» инструментами.
В целом, связка mosh+tmux+fish для меня решила множество мелких (и не очень) проблем, связанных с работой в удаленной консоли, не создав при этом много новых. Большое количество мелких, но удобных и полезных фич каждого из описанных инструментов, как мне кажется, стоит того, чтобы их попробовать самому.