tmux: terminal multiplexing
Сценарий: ты подключился к production server по SSH, запустил долгий ETL-скрипт (3 часа). Закрыл laptop. SSH-соединение разорвалось — скрипт убит сигналом SIGHUP, процесс умер. Утром выясняешь, что job не доделался, данные неполные.
Или: ты пишешь Airflow DAG в одной vim-сессии, нужно посмотреть лог в другом терминале, проверить SQL в третьем. Открываешь три SSH-сессии — три tab бараBraga, три commands ssh, три прохода аутентификации.
tmux решает обе проблемы. Это terminal multiplexer: создаёт persistent сессии, которые продолжают работать даже после disconnect, и позволяет иметь множество окон/панелей внутри одного соединения. Закрыл laptop — tmux session живёт, переподключился — tmux attach и ты снова там.
В этом уроке: установка, основные команды (sessions, windows, panes), key bindings, конфигурация, и почему каждый DE должен освоить tmux на первой неделе работы.
Установка
# Debian/Ubuntu:
sudo apt install tmux
# macOS:
brew install tmux
# Проверка:
tmux -V
# tmux 3.4 или новее
Концептуальная модель
Полная иерархия: один server, в нём sessions, в каждой session windows (как tabs), в каждом window можно делать panes (split-screen).
Базовый workflow
# Создать новую сессию:
tmux
# Или с именем:
tmux new -s work
# Внутри сессии: командная строка как обычно
$ ls
$ vim file.py
# Detach (выйти, оставив сессию работать):
# Жмём prefix + d (по умолчанию Ctrl-b, потом d)
# Список сессий:
tmux ls
# Reattach к последней:
tmux attach
# или конкретной:
tmux attach -t work
# Убить сессию:
tmux kill-session -t work
Detach/attach — main feature
Detach — это отключиться, но не убить. Все процессы продолжают работать. Это спасает жизнь:
# На сервере по SSH:
$ ssh prod-server
$ tmux new -s nightly_etl
$ python /opt/etl/long_job.py # 3-hour job
# Жмём Ctrl-b d # detach
$ exit # SSH disconnect
# ETL продолжает работать в tmux session на сервере.
# Утром:
$ ssh prod-server
$ tmux attach -t nightly_etl # снова в сессии
# Видишь логи job, или ждёшь окончания
Это — главный use case tmux для DE. Без tmux SSH disconnect = SIGHUP = job died.
SSH — ключи, туннели и сохранение соединенияKey bindings: prefix
tmux команды triggers через prefix key + другая клавиша. Default prefix — Ctrl-b. Многие пользователи переназначают на Ctrl-a (как в GNU screen).
Ctrl-b d # detach
Ctrl-b c # create new window
Ctrl-b n # next window
Ctrl-b p # previous window
Ctrl-b 0..9 # switch to window by number
Ctrl-b , # rename current window
Ctrl-b w # list windows (с preview)
Ctrl-b & # kill current window
Panes
Ctrl-b % # split horizontal (left | right)
Ctrl-b " # split vertical (top / bottom)
Ctrl-b o # next pane
Ctrl-b ; # toggle between two recent panes
Ctrl-b arrows # move to pane in direction
Ctrl-b z # zoom current pane (fullscreen, toggle)
Ctrl-b x # kill current pane
Ctrl-b q # show pane numbers (then press number to focus)
Ctrl-b { # swap pane left
Ctrl-b } # swap pane right
Ctrl-b spacebar # rotate pane layouts (presets)
Sessions
Ctrl-b s # list sessions (interactive)
Ctrl-b $ # rename current session
Ctrl-b ( # previous session
Ctrl-b ) # next session
Ctrl-b D # choose session to detach (если есть multi-user)
Copy mode
Ctrl-b [ # enter copy mode (scroll back, search)
# В copy mode: arrows для скролла, /text для search
# Space — start selection, Enter — copy
Ctrl-b ] # paste
# Vim-style keybindings (если настроено):
v # start selection
y # copy
Реальный DE workflow: production debugging
$ ssh prod-airflow
$ tmux new -s debug
# Окно 0 (rename: airflow-logs):
$ tail -f /var/log/airflow/scheduler.log
# Создать новое окно:
# Ctrl-b c
# Rename: Ctrl-b , -> "db-query"
$ psql -h analytics-db -U airflow
analytics=# SELECT count(*) FROM raw.user_events WHERE date = current_date;
# Ctrl-b c (третье окно: "fs")
$ cd /opt/airflow/dags
$ ls -lha *user_events*
# Split current window vertically (Ctrl-b "):
# Now two panes: dags listing top, htop bottom
$ htop
# Detach (Ctrl-b d), закрыл laptop. Session живёт.
# Через 30 минут переподключился:
$ ssh prod-airflow
$ tmux attach -t debug
# Всё на месте: 3 окна с logs, db, fs+htop. Production debugging session preserved.
Convention: один tmux session — одна задача. Не пихай всё в одну session. tmux new -s nightly_etl, tmux new -s migration, tmux new -s incident-2026-05-13. Когда задача закрыта — tmux kill-session. Это и organize, и encourages tidiness.
Конфигурация: ~/.tmux.conf
Default key bindings бесят опытных пользователей. Минимальная сustomization:
# ~/.tmux.conf
# 1. Change prefix to Ctrl-a (как GNU screen, проще достичь):
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# 2. Start window numbering from 1 (0 на дальнем краю клавиатуры):
set -g base-index 1
setw -g pane-base-index 1
# 3. Renumber windows on close (чтобы не было пропусков):
set -g renumber-windows on
# 4. Mouse mode:
set -g mouse on
# 5. 256 colors + true color:
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-256color:RGB"
# 6. Bigger history:
set -g history-limit 50000
# 7. Vim-style pane navigation:
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# 8. Vim-style copy mode:
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
# 9. Reload config:
bind r source-file ~/.tmux.conf \; display "Config reloaded"
# 10. Status bar:
set -g status-bg colour234
set -g status-fg colour137
set -g status-left "#[fg=green]#S "
set -g status-right "#[fg=cyan]%Y-%m-%d %H:%M"
Перезагрузка: Ctrl-a r (если уже в session) или из shell tmux source ~/.tmux.conf.
tmux в production: long-running jobs
Pattern для оркестрации длинного job:
# Запустить ETL в фоновой tmux session (detached):
tmux new -d -s etl_$(date +%Y%m%d) 'python /opt/etl/full_refresh.py 2>&1 | tee /tmp/etl.log'
# Опции:
# -d — start detached (не attach автоматически)
# -s name — задать имя
# 'cmd' — команда, которая выполнится в новой session
# Проверить статус:
tmux ls
# etl_20260513: 1 windows (created Mon May 13 14:30:00 2026)
# Подключиться посмотреть:
tmux attach -t etl_20260513
# Отключиться без killing job: Ctrl-b d
Это более robust, чем nohup + disown + background — потому что:
- Можно подключиться и посмотреть output в любой момент.
- Если процесс просит ввод — можно ответить (например, sudo password).
- Логирование в файл + интерактивный мониторинг одновременно.
Альтернатива — systemd-run для one-shot jobs: systemd-run --unit etl-2026-05-13 python /opt/etl/full.py. systemd возьмёт ownership, можно смотреть через journalctl -u etl-2026-05-13 -f. Это production-grade, tmux — для ad-hoc и debugging.
tmux + SSH: автоматическое восстановление
# В ~/.bashrc на сервере:
if [ -z "$TMUX" ] && [ -n "$SSH_TTY" ]; then
# Если зашли по SSH и не в tmux — auto-attach или создать
tmux attach -t main || tmux new -s main
fi
Теперь любой SSH-логин автоматически попадает в session main. Disconnect не теряет работу.
tmux vs screen vs alternatives
В DE-командах tmux — дефолт. Если приходишь на новое место и тебе показывают tmux session с 12 windows и Vim/htop split-screen — это норма. Освой tmux, screen учить не обязательно (можно при необходимости).
Подводные камни
1. Терминал поверх tmux поверх SSH — путаница с keybindings
Если в локальном terminal (iTerm2/Alacritty) есть tab switching на Cmd-T, а в tmux на Ctrl-b c — легко запутаться. Решение: использовать tmux только когда он нужен (production servers через SSH), а локально — нативный terminal с tab support.
2. Copy-paste между tmux и system clipboard
Стандартный tmux copy mode записывает в свой buffer, не в OS clipboard. Решение через tmux-yank plugin или tmux set -g set-clipboard on + поддержка терминала (xclip/pbcopy).
3. mouse mode иногда мешает
С set -g mouse on text selection попадает в tmux buffer, не выделяется для копирования через Cmd+C. Workaround: hold Option/Shift при выделении (терминал-dependent).
4. Different bash sessions имеют разный history
Каждый pane/window — отдельный bash process. Их history не сливается автоматически. Решение через shopt -s histappend + PROMPT_COMMAND='history -a;history -n'.
tmux + современный stack
# Запустить tmux session с готовым layout (для проекта):
#!/bin/bash
# work_session.sh
tmux new-session -d -s work
tmux send-keys -t work 'cd ~/projects/etl && nvim' C-m
tmux split-window -t work -h
tmux send-keys -t work 'cd ~/projects/etl && python -m pytest' C-m
tmux split-window -t work -v
tmux send-keys -t work 'cd ~/projects/etl && htop' C-m
tmux attach -t work
Запускаешь скрипт — session с готовым layout открывается.
Альтернатива — tmuxinator (Ruby) или tmuxp (Python) — declarative YAML configs для projects.
Cross-links
- Модуль 09 (processes) — nohup/disown как альтернативы tmux для background jobs.
- Модуль 11 (ssh) — tmux идеально работает в паре с SSH.
- Модуль 14 (systemd) — systemd-run как production-grade alternative для one-shot jobs.
- Урок 02 (fzf) — fzf для switching между tmux sessions.
Попробуй сам
-
Запусти tmux, создай 2 окна, поделай в каждом что-то, переключайся через Ctrl-b 0/1.
-
Detach (Ctrl-b d), потом
tmux ls, потомtmux attach. -
В одном окне —
top, split (Ctrl-b ”), в нижнем pane —tail -f /var/log/syslog. Переключение через Ctrl-b стрелки. -
Сохрани конфиг
~/.tmux.conf:
unbind C-b
set -g prefix C-a
bind C-a send-prefix
set -g mouse on
set -g base-index 1
В running session: Ctrl-b :source-file ~/.tmux.conf (или после reload tmux source ~/.tmux.conf).
- SSH к серверу, запусти tmux там:
ssh your-server
tmux new -s test
echo "hello"
# Ctrl-b d
exit
# Заходишь обратно:
ssh your-server
tmux attach -t test
# Видишь "hello" — session жива
- Long job через tmux:
tmux new -d -s long_job 'for i in {1..30}; do echo $i; sleep 10; done'
tmux ls
tmux attach -t long_job # подключился посмотреть прогресс
# Ctrl-b d — отключился, job продолжает