$ tmux — corso in 10 lezioni
Lezione 07 di 10

tmux è un linguaggio di comandi

Ogni scorciatoia che hai imparato finora è solo un tasto legato a un comando. Gli stessi comandi si scrivono dalla shell, si mettono in uno script, si indirizzano a un pannello che non stai nemmeno guardando.

Teoria

La stessa cosa, da tre parti

prefix c non «crea una finestra»: esegue new-window. Puoi verificarlo con tmux list-keys | grep new-window. La conseguenza è che ogni funzione di tmux ha tre accessi equivalenti, e il terzo è quello che apre le porte.

Da doveCome
tastoprefix c
riga di comando di tmuxprefix : poi new-window -n log
shelltmux new-window -n log — anche da fuori, anche da uno script

La riga di comando di tmux ha il completamento con Tab e la cronologia con le frecce. Ed è il posto dove provare un comando prima di metterlo nel file di configurazione: se funziona lì, funzionerà legato a un tasto.

Gli indirizzi

Il pezzo che rende tutto questo utile è l'opzione -t, che dice su cosa agire. La sintassi è gerarchica e si legge da sinistra:

sessione : finestra . pannello
sito:2.1          # pannello 1 della finestra 2 della sessione "sito"
sito:log        # la finestra chiamata "log", ovunque sia
sito:           # la finestra attiva della sessione "sito"
:2              # finestra 2 della sessione corrente
.1              # pannello 1 della finestra corrente
=sito           # corrispondenza esatta, non per prefisso

# abbreviazioni speciali:
-t !            # l'ultima finestra / il pannello precedente
-t {last}       # l'ultima usata
-t {next}       # la successiva
-t {end}        # l'ultima in ordine
È il motivo per cui i nomi non devono contenere punti o due punti. Una sessione chiamata sito.it rende ambiguo sito.it:1, e tmux prova a interpretare .it come indice di pannello. Trattini e trattini bassi: sempre.
Pratica

send-keys: scrivere in un pannello da fuori

send-keys inserisce testo nell'input di un pannello come se lo avessi battuto tu. È il comando che trasforma tmux da interfaccia a strumento di automazione.

esempi
$ tmux send-keys -t sito:server 'npm run dev' C-m
#                                              └── C-m = Invio

$ tmux send-keys -t sito:2.1 'tail -f /var/log/nginx/error.log' C-m
$ tmux send-keys -t deploy C-c              # manda un Ctrl-C, interrompe
$ tmux send-keys -t sito:1 'clear' C-m

# lo stesso comando in tutti i pannelli della finestra:
$ tmux list-panes -F '#{pane_id}' | xargs -I{} tmux send-keys -t {} 'date' C-m

Le chiavi speciali si scrivono con i nomi che tmux conosce: C-m o Enter per l'invio, C-c per l'interruzione, C-d, Escape, Up. Senza C-m finale il comando compare nel pannello ma non parte: è l'errore che si fa tutti la prima volta.

Attento a dove finisce. send-keys scrive nel pannello, qualunque cosa ci sia dentro: se c'è Vim aperto e non una shell, il tuo comando diventa testo nel file. Prima di automatizzare, verifica con tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_current_command}'.

Il pannello sincronizzato

scrivere in tutti i pannelli insieme
prefix : setw synchronize-panes on

# ora ogni tasto va in tutti i pannelli della finestra.
# Con quattro pannelli, ognuno in ssh su una macchina diversa,
# aggiorni quattro server scrivendo una volta sola.

# nel .tmux.conf, con un tasto e un segnale visibile:
bind S setw synchronize-panes \; display "sync: #{?pane_synchronized,ON,off}"

È potentissimo e va spento subito dopo. Dimenticarlo acceso significa lanciare per sbaglio lo stesso comando distruttivo su quattro macchine: il display nella riga qui sopra esiste apposta.

Trucco

Uno script che costruisce l'ambiente di lavoro

Se ogni mattina apri le stesse quattro finestre nella stessa disposizione, quel lavoro si scrive una volta. Lo script qui sotto crea la sessione staccata, la riempie, e alla fine ci si attacca — e se la sessione esiste già non fa nulla, si limita a entrare.

~/bin/lavoro-sito
#!/usr/bin/env bash
set -euo pipefail

SES=sito
DIR=~/progetti/sito-cliente

if ! tmux has-session -t "=$SES" 2>/dev/null; then

  # finestra 1: editor, con un pannello per git sotto
  tmux new-session -d -s "$SES" -n codice -c "$DIR"
  tmux send-keys   -t "$SES:codice" 'vim .' C-m
  tmux split-window -v -p 25 -t "$SES:codice" -c "$DIR"

  # finestra 2: server di sviluppo, acceso
  tmux new-window  -t "$SES" -n server -c "$DIR"
  tmux send-keys   -t "$SES:server" 'npm run dev' C-m

  # finestra 3: due log affiancati
  tmux new-window  -t "$SES" -n log -c "$DIR"
  tmux send-keys   -t "$SES:log" 'tail -f storage/logs/app.log' C-m
  tmux split-window -h -t "$SES:log"
  tmux send-keys   -t "$SES:log" 'tail -f /var/log/nginx/error.log' C-m

  tmux select-window -t "$SES:codice"
  tmux select-pane   -t "$SES:codice.1"
fi

# attacca da fuori, cambia client se sei già dentro tmux
if [ -z "${TMUX:-}" ]; then
  tmux attach -t "=$SES"
else
  tmux switch-client -t "=$SES"
fi
Le tre opzioni che fanno funzionare lo script: -d su new-session costruisce tutto senza attaccarsi, così i comandi successivi non vengono interrotti; -p 25 su split-window assegna al pannello nuovo il 25 per cento dello spazio; has-session con = davanti al nome evita di trovare una sessione dal nome simile.

Se preferisci un file di descrizione

Esistono strumenti che fanno la stessa cosa a partire da un file YAML invece che da uno script: tmuxinator (Ruby) e tmuxp (Python) sono i due più diffusi. Sono comodi se hai molti progetti dalla struttura simile; per due o tre ambienti, lo script di venti righe si legge meglio e non ha dipendenze da installare sul server.

lo stesso ambiente, in tmuxp
session_name: sito
start_directory: ~/progetti/sito-cliente
windows:
  - window_name: codice
    layout: main-horizontal
    panes:
      - vim .
      - git status
  - window_name: server
    panes:
      - npm run dev