Shellkonfiguration – Konsolen-Tools
Hinweis: Ich steige gerade erst von screen
auf tmux
um, bin damit also noch nicht annähernd so vertraut wie mit den anderen bisherigen Themen auf dieser Website (bash und systemd). Deshalb mag es hier in den nächsten Wochen und Monaten mit größerer Wahrscheinlichkeit als bei den übrigen Inhalten zu Änderungen kommen.
Die von tmux
verwalteten "virtuellen" Pseudoterminals unterscheiden sich (potentiell) in folgenden Kategorien:
Server(-Socket-Name)
Grundsätzlich reicht ein einziger tmux-Server pro Benutzerkonto aus, da in einem Serverprozess mehrere Sessions laufen können. Man kann aber über tmux -L socket_name
die Nutzung eines bestimmten bzw. den Start eines neuen Serverprozesses erzwingen.
Session
In einer Session werden (üblicherweise) mehrere Windows zusammengefasst. Vermutlich wird tmux zumeist mit nur einer Session genutzt (dazu mehr siehe unten). Ein tmux-Client kann immer nur mit einer Session zur Zeit verbunden sein, aber sehr leicht zwischen unterschiedlichen Sessions umschalten.
Mit Tricks (siehe unten) kann man es so aussehen lassen, als wäre dasselbe Terminal Bestandteil mehrerer Sessions.
Window
Ein tmux-Window ist der gesamte angezeigte Bildschirm. Normalerweise wird man mehrere Windows gleichzeitig haben, zwischen denen man umschaltet. Ein Window enthält eine oder mehrere Panes (d.h., wenn man ein neues Window erzeugt, wird automatisch eine Pane darin erzeugt).
Ein Window kann in eine andere Session verschoben oder in eine andere Session verlinkt werden (so dass es in beiden Sessions sichtbar ist).
Pane
Eine tmux-Pane ist das eigentliche "virtuelle" Pseudoterminal. Wenn man nicht aktiv mit Panes arbeitet, kann man durchaus nicht einmal ahnen, dass es diese Objekte gibt, weil sie dann nicht erkennbar angezeigt werden. Aus der Sicht des Betrachters ist eine Pane dann dasselbe wie ein Window.
Ein typischer Fall der tmux-Nutzung (tmux-Konfigurationsdatei, nicht verantwortlich für den Shell-Prompt oder die Terminalfarben):
ec:0 02:09:36 hl@notebook:~
start cmd:>
C-q --- 0:local-1- 1:local-2* 2:host-1 3:host-2 4:host-3 5:host-4
Links in der Statuszeile wird ein abweichender escape key angezeigt (Standard ist Ctrl-b); das ist besonders bei (über SSH hinweg) verschachtelten tmux
-Instanzen nützlich, um leichter den Überblick zu behalten.
Die einzelnen Fenster kann man jeweils manuell nach dem Start von tmux erzeugen oder automatisch beim Start erzeugen lassen (in der Konfigurationsdatei).
Wenn tmux
noch nicht läuft, kann man es einfach mit tmux
starten; wenn keine Kommandos übergeben werden, wird new-session
angenommen. Wenn es schon läuft, kann man die laufende Session mit tmux attach
betreten. Man kann sich aber auch eine kleine Shellfunktion definieren, so dass tmux
je nach Situation eine neue Session startet oder sich an die bestehende hängt. Wenn irgendwelche Argumente auf tmux
folgen, wird die eingegebene Kommandozeile ausgeführt, unabhängig vom Zustand des Systems.
Der folgende Code ist für bash
.
tmux () { if [ $# -eq 0 ]; then command tmux attach-session; else command tmux "$@"; fi }
Eine platzsparende und in der Navigation sehr effiziente Arbeitsweise ist:
ein oder mehrere Fenster (je nach typischem Bedarf) für lokale Shells
ein Fenster pro System, mit dem man sich per SSH verbindet
eine weitere tmux-Instanz auf den SSH-Zielsystemen (die lokale und die jeweilige ferne tmux-Instanz wissen nichts voneinander, aber aus praktischen Gründen sollte man unbedingt unterschiedliche escape keys verwenden)
Nach meinem Empfinden ist es besser, wenn der escape key für alle innersten Instanzen derselbe ist. Eine innerste Instanz sollte genauso zu bedienen sein wie eine nicht verschachtelte Instanz. Beide sind die "normale" Situation. Eine tmux-Instanz zu starten, in der (mindestens) eine weitere Instanz laufen soll, ist der Spezialfall, der eine abweichende Steuerung haben sollte. Für einen intuitiven Bezug zwischen der Verschachtelung und der Position der escape keys sollte derjenige der inneren Instanz weiter "oben" auf der Tastatur liegen. Ich nutze Ctrl-q für die innerste Instanz und Ctrl-a für die äußerste. Beides lässt sich sehr gut mit nur der linken Hand drücken.
tmux-Konfigurationsdatei der inneren tmux-Instanz; tmux-Konfigurationsdatei der äußeren tmux-Instanz
ec:0 01:38:48 hl@notebook:~
start cmd:>
C-q host-1 --- 0:home* 1:systemd 2:scripts
C-a --- 0:local-1- 1:local-2 2:host-1* 3:host-2 4:host-3 5:host-4
Die am häufigsten benutzten Systeme und (auf dem entfernten System) Verzeichnisse bzw. Anwendungen auf festen Positionen innerhalb von tmux zu haben, bringt den großen Vorteil mit sich, dass man quasi "blind" (ohne Blick auf den Bildschirm) zwischen den entsprechenden Terminals wechseln kann.
Wenn man (z.B. via SSH) häufig mit denselben Systemen zu tun hat, insbesondere bei häufigen, schnellen Wechseln zwischen den Systemen, ist es hilfreich, wenn sich diese Systeme (also die Konsolen mit der SSH-Verbindung zu dem jeweiligen System) immer an derselben Stelle befinden. Wenn man insgesamt nur mit sehr wenigen (<10) Systemen zu tun hat, kann man in derselben tmux-Ansicht alle diese Systeme unterbringen.
Der häufigere Fall dürfte sein, dass man nicht alle Systeme (plus mindestens eine Konsole für das lokale System) in die maximal zehn leicht erreichbaren tmux-Fenster bekommt (vielleicht möchte man auch nicht zehn Fenster nutzen, weil wegen langer Fensternamen nicht alle gleichzeitig in das Konsolenfenster passen wie in den Beispielen hier).
Eine komfortable Lösung dafür ist, die Systeme in unterschiedlichen Sessions zusammenzufassen. Das Kriterium für die Gruppierung sollte den Komfort des Nutzers maximieren. Eine sinnvolle Möglichkeit ist die Gruppierung nach Umgebungen (plus eine Gruppe für die lokalen Konsolen): Produktion, Integration, Test.
Eine Session ist eine Gruppe von Fenstern (mindestens eins). Auf demselben tmux-Server können mehrere Sessions laufen (zumindest eine). Ein tmux-Client (die Software, deren Ausgabe man unmittelbar sieht und bei der die Tastatureingaben (zuerst) landen) kann nur mit einer Session zur Zeit verbunden sein, aber man kann leicht zwischen mehreren Sessions umschalten (ohne den Client zu beenden). Auf diese Weise kann man die Systeme mit derselben Funktion in Produktion und Integration auf die Position 0 (Window 0) legen, jeweils in den Sessions prod und int. Dadurch kann man problemlos mehr als zehn Systeme auf jeweils derselben Position haben, so dass man "blind" (ohne Blick auf den Bildschirm) umschalten kann. Mit zwei Tastenkombinationen hintereinander (die erste für die Session, die zweite für das Fenster) kann man die gewünschte Konsole erreichen. Wenn die Systeme immer an derselben Position liegen, weiß man nach kurzer Zeit, über welche Fensternummer man ein System erreicht, so dass man nicht in der Statusleiste nachlesen muss.
ec:0 01:38:48 hl@notebook:~
start cmd:>
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Auch in nicht verschachtelten Setups hat man mindestens eine Session (wenn man keine in der Konfigurationsdatei definiert, wird eine ohne Namen automatisch erzeugt).
Die Option -A
für new-session
und -S
für new-window
verhindern, dass tmux beim Neuladen der Konfiguration im laufenden Betrieb eine Fehlermeldung ausgibt, wenn schon eine Session mit demselben Namen existiert. Statt dessen wechselt der tmux-Client zu dieser Session (wie bei attach-session
). In ähnlicher Weise wird kein neues Fenster erzeugt, wenn schon eins mit demselben Namen in derselben Session existiert, und (wie bei select-window
) das existierende Fenster aktiviert (-d
verhindert, dass das Fenster aktiviert wird).
new-session -A -s local -n 'local-1' set-option status-style bg=brightgreen,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'local-2' new-window -S -d -n 'local-3' new-session -A -s prod -n 'prod-host-1' set-option status-style bg=yellow,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'prod-host-2' new-window -S -d -n 'prod-host-3' new-session -A -s int -n 'int-host-1' set-option status-style bg=cyan,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'int-host-2' new-window -S -d -n 'int-host-3' new-session -A -s test -n 'test-host-1' set-option status-style bg=green,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'test-host-2' new-window -S -d -n 'test-host-3'
Es gibt mehrere Möglichkeiten, zwischen Sessions umzuschalten:
Wenn man den Namen des benötigten tmux-Kommandos weiß, kann man es über den tmux-Kommandoprompt (zu erreichen über tmux escape key-:) eingeben, z.B. switch-client
(Auswahl der Session, auch wenn der Name das nicht nahelegt), select-window
, select-pane
Das ist oftmals nicht praktikabel. Einerseits kann man sich üblicherweise nicht viele Kommandos merken, die man nur selten benutzt, andererseits wird das Problem durch die Optionen der Kommandos noch verschlimmert. Obendrein dauert es relativ lange, ist also wenig praktikabel für häufig genutzte Aktionen. Eine gewisse Hilfe ist die Tab-Vervollständigung der Kommandos.
Man kann einen Menüeintrag nutzen (siehe den nächsten Abschnitt).
Man kann ein Kommando an eine Tastenkombination binden. Über 50 dieser Belegungen sind vordefiniert (können aber verändert werden). Eine der nützlichsten für Anfänger ist tmux escape key-?, wodurch die aktuelle Belegung angezeigt wird.
Dies ist vor allem für die am häufigsten benötigten Aktionen relevant. Naturgemäß ist die Menge der Kombinationen, die man sich merken kann, begrenzt. Ebenso die Anzahl der nicht vorbelegten Kombinationen; solche anderweitig zu verwenden mag hinderlich sein, wenn man tmux auf einem anderen System nutzen muss, ohne dort die gewohnte Konfiguration übernehmen zu können.
Allerdings erscheint die Menüvariante sinnvoller. Man braucht bloß eine zusätzliche Taste(nkombination) in der Tastenfolge, verbraucht aber nicht gleich eine ganze Reihe der "kostbaren" normalen Tastenbelegungen.
Kategorie | benötigte Tastenfolgen (Beispiele) |
---|---|
direkte Tastenbelegung | Ctrl-l Ctrl-p Ctrl-i Ctrl-t |
Menüeintrag | Ctrl-s l Ctrl-s p Ctrl-s i Ctrl-s t |
Ein weiterer Vorteil der Menüvariante ist, dass man die Tastenbelegungen über Systeme hinweg einheitlicher gestalten kann. Wenn man die Tasten nach den Session-Namen wählt, hat man auf unterschiedlichen Systemen womöglich unterschiedliche Namen und dadurch Kollisionen mit anderen Tastenbelegungen. Ctrl-s kann man aber einheitlich für das Session-Menü verwenden.
Im einfachsten Fall fügt tmux
dem Terminal nur eine Statuszeile hinzu. Man kann aber viel mehr als das konfigurieren. Das ist natürlich einerseits sehr subjektiv, andererseits hängt von der Arbeitsweise ab, was sinnvoll erscheint (etwa, ob man mit mehreren Sessions arbeitet). Mein tmux
sieht (je nach Situation) ungefähr so aus (bis auf die Farben):
─↓Pane 0 (ID %14 TTY /dev/pts/19) active (zoomed)─────────────────────────────────────────────────
──Pane 0 (ID %0 TTY /dev/pts/1) inactive (sync──┬──Pane 1 (ID %5 TTY /dev/pts/8) active (synced)──
│
ec:0 22:11:55 root@selinux-test:~ │ec:0 22:11:55 root@selinux-test:~
start cmd:> echo foo │start cmd:> echo foo
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
C-e Host: selinux-test Session: main 1:home* 2:scripts 3:tmp- 4:man 5:bash
C-q Host: notebook Session: ssh 1:local 2:selinux-test*Z 3:selinux_laging- 4:selinux_testuser
Durch die Titelzeile verliert man pro pane eine Zeile, aber bei typischen Terminalgrößen ist das kein Problem. Die Information, dass in einem window eine pane gezoomt oder markiert ist, bekommt man allerdings auch in der Statuszeile. Pane index, ID und TTY brauche ich nur selten, und inzwischen gibt es im pane-Menü einen Eintrag, der das gut anzeigt. Die aktive pane wird farblich hervorgehoben, diese textliche Unterscheidung ist also auch nicht mehr wichtig. Aber ich mag den Hinweis, dass eine pane gezoomt oder Teil der synchronisierten Eingabe ist.
set -g pane-border-status top set -g pane-border-indicators both set -g pane-border-format "Pane #{pane_index} (ID #{pane_id} TTY #{pane_tty}) #{?pane_active,active,inactive}#{?window_zoomed_flag, (zoomed),}#{?pane_synchronized, (synced),}#{?pane_marked, (marked),}#{?pane_input_off, (input disabled),}" set pane-border-style fg=#777777
Man kann eine Tastenkombination an ein Menü binden und über das Menü eins von mehreren Kommandos auswählen (über die Pfeiltasten oder die dem Eintrag zugewiesene Ziffer oder Taste). Soweit praktikabel nehme ich die Standardbelegung als Menü-Auswahltaste, so dass man das Menü auch dafür verwenden kann, die Standardbelegungen nachzulesen.
Menüs eignen sich dafür, mit wenigen Tastenkombinationen viele Kommandos zu erreichen. Meine Empfehlung ist:
Ein Menü für die Kommandos mit Session-Bezug (bei mir auf Ctrl-s):
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─Session commands───────────────────────────────────────────┐
│ List sessions │
│ Show session tree (w) │
│ New session │
│ Rename session ($) │
│ Kill session │
├────────────────────────────────────────────────────────────┤
│ Switch to another session │
│ Switch to local (l) │
│ Switch to prod (p) │
│ Switch to int (i) │
│ Switch to test (t) │
│ Switch to previous session (() │
│ Switch to next session ()) │
│ Switch to other session │
├────────────────────────────────────────────────────────────┤
│ Convert current session to group (for independent sharing) │
│ Link the current window to another session │
│ Unlink window from this session (C-u) │
└────────────────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Man kann damit auch "blind" in eine Session springen. In die Session prod kann man mit dieser Tastenfolge springen: escape key Ctrl-s p
Die Zeilen der Art Switch to local sind spezifisch für das jeweilige System. Den Rest kann man einheitlich überall so verwenden.
Ein Menü für die Kommandos mit Pane-Bezug (bei mir auf Ctrl-p):
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─Pane commands───────────────────────────────────┐
│ ----- Change pane status ----- │
│ Zoom pane in/out (z) │
│ (Un)Mark the current pane (m) │
│ Enable/disable input * (e) │
│ Enable/disable synced input * (s) │
│ Enable synced input for all panes * (a) │
│ Disable synced input for all panes * (n) │
│ ----- Add/remove panes ----- │
│ Add pane to the right (%) │
│ Add pane below (") │
│ Go to neighbour pane (arrow left/up/right/down) │
│ Go to next pane │
│ Kill pane (x) │
│ join marked pane to the right * (j) │
│ join marked pane below │
│ Move pane to new window (!) │
│ ----- Change pane position / layout ----- │
│ Swap pane with the marked one │
│ Rotate panes (C-o) │
│ Layout even-horizontal (M-1) │
│ Layout even-vertical (M-2) │
│ Layout main-horizontal (M-3) │
│ Layout main-vertical (M-4) │
│ Layout tiled (M-5) │
├─────────────────────────────────────────────────┤
│ List panes (in all sessions, with ID and TTY) │
│ Find pane with a certain tty │
│ Display pane indexes (not the IDs!) (q) │
└─────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Ein Menü für die Kommandos zum Wechsel in den copy mode und zur Nutzung der buffer (bei mir auf Ctrl-y):
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─Copy & Paste─────────────────────────────────────┐
│ Switch to copy mode ([) │
│ Switch to copy mode and go back (PageUp) (PPage) │
│ Choose buffer (=) │
│ Rename buffer (r) │
│ Show current buffer name (c) │
│ Paste current buffer (v) │
│ ----- mark / copy (vi mode keys) ----- │
│ Begin selection (Space) │
│ Copy selection and exit copy mode (Enter) │
│ Copy selection and stay in copy mode * (y) │
│ Exit copy mode (q / Esc) (q) │
└──────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Ein Menü für die Kommandos im copy mode (bei mir auf Ctrl-y):
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─Copy mode keys (vi mode)───────────────────┐
│ Begin selection (Space) │
│ Copy selection and exit copy mode (Enter) │
│ Copy selection and stay in copy mode * (y) │
│ Clear selection (Escape) │
│ Exit copy mode (q) │
└────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Ein Menü für die übrigen Kommandos – gewissermaßen das "Startmenü" (bei mir auf Space):
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─command collection────────────────────────────────────────┐
│ Detach tmux client (d) │
│ Go (back) to the last active window (prefix: C-q) │
│ Go one window to the left (p) │
│ Go one window to the right (n) │
│ Create additional window (c) │
│ Rename window * (A) │
│ Change our escape / leader key / prefix │
│ Change inner tmux's escape / leader key / prefix │
│ Send prefix (enter the escape / leader key literally) │
│ List windows (this session only; with ID and pane count) │
│ Move current window to the left (Left) │
│ Move current window to the right (Right) │
│ Set window size to smallest client │
│ Set window size to largest client │
│ Set window size to safe for serial console (79×24) │
│ Switch to copy mode and go back (PageUp) (PPage) │
│ Popup window with shell (exit with <Esc>) * (M-w) │
│ Start inner tmux (input shell code without execution) │
├───────────────────────────────────────────────────────────┤
│ Session menu * (C-s) │
│ Pane menu * (C-p) │
│ Copy mode & buffer menu * (C-y) │
│ Control character menu (Enter) │
│ Escape sequence menu (Backspace) │
│ Unicode input / wrong keyboard layout: U Enter (two keys) │
├───────────────────────────────────────────────────────────┤
│ Kill pane (x) │
│ Kill window (&) │
│ Kill session │
│ Kill server │
│ Lock client │
│ Reload config file │
│ Command prompt (:) │
└───────────────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Das beste Menü hilfe jemandem nicht, der von dem Konstrukt nichts weiß. Deshalb wird beim Start von tmux
ein entsprechender Hinweis in der Statuszeile angezeigt, der beim ersten (technisch: bei jedem) Aufruf des Menüs überschrieben wird. Dies ist realisiert über:
%hidden STATUS_LEFT_INITIAL="Open main menu with #{prefix} <Space>---Host: #h Session: #{session_name}---" %hidden STATUS_LEFT="#{prefix} Host: #h Session: #{session_name}---" bind-key Space { set -g status-left "${STATUS_LEFT}" ; display-menu -T 'command collection' ...
Es gibt Situationen, in denen die Eingabe bestimmter Zeichen(folgen) in der Konsole nicht leicht oder nicht auf dem normalen Weg möglich ist:
normale Arbeitssituation
Der Terminal-Emulator fängt bestimmte Tasten ab
Soweit man das konfigurieren kann, lässt sich das Problem entschärfen, indem man die Funktionen des Terminal-Emulators auf Tastenkombinationen legt, die das Terminal nicht (er)kennt, z.B. Shift-Ctrl-wasauchimmer.
Man weiß die benötigte Tastenkombination nicht (z.B. Abbrechen von ssh
, telnet
, virsh console
, qm terminal
)
nicht terminal-spezifisch: Die Zeichen sind nicht direkt über die Tastatur erreichbar (oder es ist nicht klar, wie)
Problemsituationen
Man hat die falsche Tastaturbelegung, entweder durch die falsche physische Tastatur oder weil das System nicht korrekt hochfährt
Für dieses Szenario ist es natürlich immens wichtig, dass die entsprechenden tmux
-Funktionen über Tasten erreichbar sind, die davon nicht betroffen sind. Die hier vorgeschlagene Konfiguration nutzt für die wichtigsten Funktionen die Tasten Space, Enter, S, P, X, V und U. Diese sind bei den wichtigsten Tastaturlayouts identisch:
QWERTY in diversen Varianten
englisch
spanisch
italienisch
nordisch (Skandinavien)
türkisch
…
QWERTZ (deutsch)
AZERTY (französisch)
Bei verschachtelten tmux
-Instanzen muss man den leader key (Präfix) für die innere Instanz anders eingeben.
Dieses Problem lässt sich bei häufig genutzten Verschachtelungen natürlich leicht durch entsprechend unterschiedliche Konfigurationen vermeiden. Aber nicht jeder tmux
-User weiß, wie das geht. Und in Problemsituationen hat man zumeist wenig Bedarf an zusätzlichem Rechercheaufwand.
Die Übersicht der Zeicheneingabe-Menüs erreicht man bei mir über leader U Enter.
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─character input menu overview───────────────────────────────────────┐
│ Control character menu (Enter) │
│ Escape sequence menu (Backspace) │
│ (for wrong keyboard layout) shell characters: X │
│ (for wrong keyboard layout) non-shell ASCII special characters: V │
│ ----- The Unicode menus are activated via two keys: U + menu index │
│ space, hyphen, superscripts, subscripts: U 1 │
│ quotes, arrows, math symbols: U 2 │
│ ASCII alphabet lowercase: U 5 │
│ ASCII alphabet uppercase: U 6 │
│ language-specific characters - de/German: U 7 │
└─────────────────────────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Ein Menü für die Eingabe von Sonderzeichen (bei mir auf Enter).
Dieses Menü nutze ich häufig (das hat natürlich mit den Umständen meiner Arbeit zu tun und mag bei anderen Linuxern anders aussehen). Man kann es auch dafür nutzen, die Bedeutung der Steuerzeichen und die Zuordnung von backslash escapes zu Steuerzeichen nachzulesen. Ich habe es auf Enter gelegt, weil
es leicht erreichbar sein soll (auch wenn das objektiv hier nicht so relevant ist; die Session-Umschaltung nutze ich 10-100mal häufiger)
es leicht zu merken ist ("Enter = Zeicheneingabe")
es auch in Problemsituationen (leicht) erreichbar sein soll; wenn dieses Menü nicht zur Verfügung steht, ist (in einer häufigen Situation) die Eingabe bestimmter Zeichen für mich faktisch unmöglich
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting control characters─────────────┐
│ exit virsh console / telnet (^]) (]) │
│ exit qm terminal (^O) (o) │
│ abort SSH connection (\n ~ .) (~) │
│ detach docker (^P ^Q) │
├──────────────────────────────────────────┤
│ tab: (^I) (i) │
│ backspace: (^H) (h) │
│ linefeed / newline: \n (^J) (j) │
│ carriage return: \r (^M) (m) │
├──────────────────────────────────────────┤
│ SIGINT: (^C) (c) │
│ SIGQUIT: (^\) (\) │
│ suspend foreground process(es): (^Z) (z) │
│ end of file: (^D) (d) │
│ flow control XOFF: (^S) (s) │
│ flow control XON: (^Q) (q) │
│ input next character literally: (^V) (v) │
├──────────────────────────────────────────┤
│ record separator: (^^) (^) │
│ unit separator: (^_) (_) │
│ file separator: (^\) (\) │
│ NUL: (^@) (@) │
│ Escape: (\e / ^[) ([) │
├──────────────────────────────────────────┤
│ Ctrl-a: (^A) (a) │
│ Ctrl-b: (^B) (b) │
│ Ctrl-e: (^E) (e) │
│ Ctrl-f: (^F) (f) │
│ Ctrl-g: (^G) (g) │
│ Ctrl-k: (^K) (k) │
│ Ctrl-l: (^L) (l) │
│ Ctrl-n: (^N) (n) │
│ Ctrl-p: (^P) (p) │
│ Ctrl-r: (^R) (r) │
│ Ctrl-t: (^T) (t) │
│ Ctrl-u: (^U) (u) │
│ Ctrl-w: (^W) (w) │
│ Ctrl-x: (^X) (x) │
│ Ctrl-y: (^Y) (y) │
└──────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Ein Menü für die Eingabe von Steuerzeichen / Steuersequenzen ist aus mehreren Gründen sinnvoll:
Nicht jeder weiß, wie man diese eingibt bzw. was man für welche Funktion benötigt.
Eventuell kann man die Zeichen auf normalem Weg gar nicht eingeben. So funktioniert etwa in WSL (Windows Subsystem for Linux) die Eingabe von Ctrl-] (zum Abbruch von telnet
oder virsh console
) nicht.
Natürlich kann man Enter nicht als Auswahltaste im Menü definieren, weil das die Menüfunktion ruiniert. Deshalb steht hier (Enter) nur im Text, als Hinweis darauf, dass man Enter benutzen kann, wenn man nicht das Menü nutzt.
Ein Menü für die Eingabe von Escapesequenzen (bei mir auf Backspace):
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting escape sequence─┐
│ F1: (\eOP) (1) │
│ F2: (\eOQ) (2) │
│ F3: (\eOR) (3) │
│ F4: (\eOS) (4) │
│ F5: (\e[15~) (5) │
│ F6: (\e[17~) (6) │
│ F7: (\e[18~) (7) │
│ F8: (\e[19~) (8) │
│ F9: (\e[20~) (9) │
│ F10: (\e[21~) (0) │
│ F11: (\e[23~) │
│ F12: (\e[24~) │
└───────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Ein Menü für die Eingabe von Escapesequenzen kann aus mehreren Gründen sinnvoll sein:
Nicht jeder weiß, wie man diese eingibt bzw. was man für welche Funktion benötigt. Dies mag im Umgang mit readline key bindings sehr hilfreich sein.
Eventuell kann man die Zeichen auf normalem Weg gar nicht eingeben, etwa weil der Terminal-Emulator F1 selber belegt hat.
Dies ist nicht für normale Situationen gedacht, sondern als Rettungsring, wenn die normale Eingabe nicht möglich ist. Deshalb liegt dieses Menü bei mir auf X, weil dies auf den meisten Tastaturen an derselben Stelle ist. Wenn man ein System, das nicht mehr ordentlich startet (was zugegebenermaßen auch die Funktion von tmux
beeinträchtigen kann), reparieren muss, aber die Shell-Zeichen nicht (oder nur sehr aufwendig) eingeben kann, hat man ein ernstes Problem.
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting keys (with wrong keyboard layout): shell───────────────────┐
│ / : paths: ls -l /var/log │
│ - : options: ls -l │
│ * : dynamic globbing: ls -l *.txt │
│ ? : 1-char globbing: ls -l app.log.? │
│ [ : character list globbing: ls -l *[^~] │
│ ] : character list globbing: ls -l /proc/[1-9]*/exe │
│ ' : quoting without expansion: echo '$HOME' │
│ " : quoting without expansion: echo "5 + 3 = $((5+3))" │
│ $ : parameter expansion / command substitution │
│ | : pipeline / OR list: echo foo | grep oo / echo foo || echo bar │
│ & : background execution / AND list: cmd & / echo foo && echo bar │
│ = : variable assignment: text=foo │
│ \ : escaping shell characters: echo foo \; bar "foo \$HOME bar" │
│ < : input redirection: cat < /path/to/file │
│ > : output redirection: echo foo | grep oo >results.txt │
│ ; : end a command: echo foo; echo bar │
│ ( : subshell: ( echo execute in separate environment ) │
│ ) : command substitution / arithmetic evaluation: $() / $((1+1)) │
│ { : command grouping (start): { echo foo; echo bar; } >output.txt │
│ } : command grouping (end): testfunc () { echo foo; } │
│ # : start comment: chmod 700 "$HOME" # fix permissions │
└──────────────────────────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Die übrigen ASCII-Sonderzeichen (auf V)
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting non-shell ASCII special characters─┐
│ ^ │
│ ° │
│ ! │
│ § │
│ % │
│ @ │
│ € │
│ , │
│ : │
│ . │
│ _ │
│ @ │
│ € │
│ µ │
│ ` │
│ ´ │
│ ² │
│ ³ │
└──────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Dieser Teil ist nicht spezifisch für Terminals. Ich hatte immer eine Textdatei, aus der ich Sonderzeichen kopieren kann. Da ich nun sowieso schon eine Eingabefunktion für Zeichen in tmux
habe und tmux
sowieso immer läuft, liegt es nahe, die Eingabemenüs um Unicode-Zeichen zu erweitern.
Da es sehr viele Unicode-Zeichen gibt (und nicht jeder dieselben wichtig findet), habe ich das Unicode-Menü zweiteilig gestaltet:
bind U switch-client -T multikey_unicode bind-key -T multikey_unicode Enter display-menu ...
Durch die Definition einer weiteren key binding table kann man mit einer zweistufigen Auswahl arbeiten:
Mit U (in Debian nicht vorbelegt; in den meisten Tastaturlayouts identisch) schaltet man in die Unicode-Tabelle um (dies hat keinen sichtbaren Effekt).
Mit Enter bekommt man eine Übersicht der verfügbaren Zeicheneingabe-Menüs (nicht nur Unicode).
Mit 1 bis 9 (ich habe nicht alle belegt) ruft man unterschiedliche Gruppen auf.
Auf den ersten Ziffern habe ich allgemeine Zeichen, auf 5 und 6 das ASCII-Alphabet (Klein- und Großbuchstaben).
Ab 7 habe ich länderspezifische Zeichen. Dies ist für Problemsituationen gedacht, mag aber auch nützlich sein für Leute, die regelmäßig Zeichen aus unterschiedlichen Sprachen benötigen.
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting unicode characters (1)───────────────────┐
│ Byte Order Mark little endian (U+FEFF) (BOM): │
│ non-breaking space (U+202F) │
│ zero-width space (U+200B) │
│ - ASCII hyphen-minus (U+002D) │
│ ‐ hyphen (U+2010) │
│ ‑ non-breaking hyphen (U+2011) │
│ soft hyphen (U+00AD) (line break only) │
│ – en dash (U+2013) (range; spaced sentence break) │
│ — em dash (U+2014) (unspaced sentence break) │
│ ― horizontal bar (U+2015) │
│ ‒ figure dash (U+2012) (digit seperator) │
│ − minus sign (U+2212) │
│ ⁻ superscript minus (U+207B) │
│ ₋ subscript minus (U+208B) │
│ ⁰ (U+2070) │
│ ¹ (U+00B9) │
│ ² (U+00B2) │
│ ³ (U+00B3) │
│ ⁴ (U+2074) │
│ ⁵ (U+2075) │
│ ⁶ (U+2076) │
│ ⁷ (U+2077) │
│ ⁸ (U+2078) │
│ ⁹ (U+2079) │
│ ₀ (U+2080) │
│ ₁ (U+2081) │
│ ₂ (U+2082) │
│ ₃ (U+2083) │
│ ₄ (U+2084) │
│ ₅ (U+2085) │
│ ₆ (U+2086) │
│ ₇ (U+2087) │
│ ₈ (U+2088) │
│ ₉ (U+2089) │
└────────────────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Vor der Erstellung dieses Menüs hatte ich nur eine sehr unscharfe Vorstellung davon, wann die unterschiedlichen typographischen Striche eingesetzt werden und wie die Zeichen heißen. Ich habe diese Gelegenheit genutzt, das Menü mit entsprechenden Hinweisen zu versehen,
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting unicode characters (2)─┐
│ ‚ (U+201A) │
│ ‛ (U+201B) │
│ „ (U+201E) │
│ ‟ (U+201F) │
│ ʼ (U+02BC) (apostrophe) │
│ « (U+00AB) │
│ » (U+00BB) │
│ ‹ (U+2039) │
│ › (U+203A) │
│ • (U+2022) │
│ → (U+2192) │
│ ← (U+2190) │
│ ↓ (U+2193) │
│ ↑ (U+2191) │
│ ↔ (U+2194) │
│ ± (U+00B1) │
│ + (U+002B) │
│ − (U+2212) │
│ × (U+00D7) │
│ ÷ (U+00D7) │
│ ≠ (U+2260) │
│ ‰ (U+2030) │
│ ☺ (U+263A) │
│ ☐ (U+2610) │
│ … (U+2026) │
└──────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
ec:0 01:38:48 hl@notebook:~
start cmd:>
┌─inserting unicode characters: de/German─┐
│ ä │
│ Ä │
│ ö │
│ Ö │
│ ü │
│ Ü │
│ ß │
└─────────────────────────────────────────┘
C-q prod-host-1 --- 0:home* 1:systemd 2:scripts
C-a Session: prod --- 0:prod-host-1* 1:prod-host-2 2:prod-host-3-
Leider kann tmux
gelinde gesagt nicht gut mit Menüs umgehen, die mehr Zeilen benötigen, als das Terminal (in dem Moment) hat. Es wird nicht etwa gescrollt oder in zwei Spalten angezeigt; nein, das Menü wird einfach überhaupt nicht angezeigt. Das kann natürlich extrem problematisch sein, vor allem für diejenigen User, die nicht wissen, wie man die benötigte Funktion anderweitig aufruft, und die dann entweder (potentiell aufwendig) recherchieren müssten oder gleich ganz aufgeben. Zwei Möglichkeiten bieten sich an, wenn man dies vermeiden will; beide setzen voraus, das man sich auf eine Untergrenze festlegt, einen Wert, der nie unterschritten wird. Standard-VGA ist 80×25, aber in einer seriellen Konsole (Proxmox-VM) mit TERM=linux
sehe ich 80×24. 24 dürfte eine sinnvolle Untergrenze sein. Da das Menü umrahmt wird, darf es also maximal 22 Zeilen haben.
Man kann sich grundsätzlich auf 22 Einträge beschränken.
Man kann eine bedingte Konfiguration nutzen: Man testet die aktuelle Größe des tmux
-Terminals, und wenn es groß genug ist, zeigt man die kritischen Menüs mit mehr Einträgen an. Leider ist es mir nicht gelungen, das "elegant" (%if "#{e|>=:#{client_height},30}"
) umzusetzen, sondern nur mit dem Umweg über Shell-Kommandos via if-shell
:
if-shell '[ #{client_height} -ge 35 ]' { bind-key Space { set -g status-left "${STATUS_LEFT}" ; display-menu -T 'command collection' \ [...] long version } { bind-key Space { set -g status-left "${STATUS_LEFT}" ; display-menu -T 'command collection' \ [...] short version }
Beim Start von tmux
wird zuerst der Server (inklusive der Menüs) konfiguriert und dann mit dem Client verbunden. Da sich die (standardmäßige) Fenstergröße nach dem Client richtet, führt dies zu dem Problem, dass die oben gezeigte Auswertung dann noch nicht möglich ist – man bekommt immer das kleine Menü (bis man manuell die Konfiguration erneut lädt).
Die einzige mir bekannte passable Lösung ist, die Definition der Menüs in zwei (eine einheitliche und ggf. eine an das System angepasste) Dateien auszulagern und diese Dateien sowohl direkt beim Einlesen der Konfiguration (das ist nötig für den Fall, dass man sie später neu lädt) als auch per hook beim Verbinden eines Clients (das ist für den Start nötig) oder der Änderung der Fenstergröße einzubinden:
source-file ~/.tmux.menus-common.conf set-hook -g client-attached "source-file ~/.tmux.menus-common.conf" set-hook -g client-resized "source-file ~/.tmux.menus-common.conf"
Unabhängig von tmux
ärgere ich mich seit langem darüber, dass Zeilenumbrüche in bash
(muss nichts mit bash
zu tun haben, ich nutze keine andere Shell) in der seriellen Konsole zumeist großes Unheil anrichten. Das Problem ist rein optischer Natur, die Kommandos funktionieren noch, wie sie sollen. Aber das Bearbeiten von langen Kommandozeilen wird dadurch nahezu unmöglich.
Lange dachte ich, dass ein Terminal-Multiplexer wie tmux
oder screen
diese Probleme nur vergrößern würde, aber es hat sich herausgestellt, dass das nicht der Fall ist. Ich habe immer noch das Problem, dass tmux
standardmäßig über die erkannte Terminalgröße hinausschreibt (nur in die Breite). Ein Work-around für dieses Ärgernis ist, das tmux
-Window eine Spalte schmaler (und zur Sicherheit eine Zeile niedriger) zu machen:
resize-window -x 79 -y 24
Auf unterschiedlichen (Gruppen von) Systemen und für unterschiedliche User will man wahrscheinlich unterschiedliche tmux
-Konfigurationen haben (vor allem für die Session-Konfigurationen), alleine schon für die unterschiedlichen leader keys in verschachtelten tmux
-Instanzen. Gleichzeitig will man den Aufwand minimieren, wenn man seine grundsätzliche Konfiguration ändert und diese Änderungen überall ausrollen will.
Eine einfache Lösung dafür ist,
eine allgemeine Konfigurationsdatei (.tmux.common.conf
) zu haben, die überall gleich ist,
diese Datei zu Beginn der .tmux.conf
einzubinden
und die system- und userspezifischen Abweichungen im Rest der .tmux.conf
zu überschreiben.
~/.tmux.conf
source-file ~/.tmux.common.conf unbind C-b set -g prefix C-q set -g prefix2 C-b bind-key C-q last # colour is one of: black, red, green, yellow, blue, magenta, cyan, white; (if supported) brightred, brightgreen, brightyellow new-session -A -s main -n 'home' set-option status-style bg=brightgreen,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'vim' new-window -S -d -n 'tmp' new-window -S -d -n 'man' new-window -S -d -n 'bash' new-session -A -s www -n 'home' set-option status-style bg=cyan,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'foo-1' new-window -S -d -n 'foo-2' new-window -S -d -n 'bar-1' new-window -S -d -n 'bar-2' new-session -A -s ssh -n 'local' set-option status-style bg=yellow,fg=black set-window-option window-status-current-style bg=white new-window -S -d -n 'host-1' new-window -S -d -n 'host-2' attach-session -t main
~/.tmux.menus.conf
if-shell '[ #{client_height} -ge 23 ]' { bind-key C-s display-menu -T 'Session commands' \ 'List sessions' '' 'list-sessions' \ 'Show session tree (w)' '' 'choose-tree -Zw' \ 'New session' '' 'command-prompt { new-session -s "%%" }' \ 'Rename session' & 'command-prompt -I "#S" { rename-session "%%" }' \ 'Kill session' '' 'confirm-before -p "kill-session #S? (y/n)" kill-session' \ '' \ ' Switch to another session' '' '' \ 'Switch to main' m 'switch-client -t main' \ 'Switch to www' w 'switch-client -t www' \ 'Switch to ssh' s 'switch-client -t ssh' \ 'Switch to previous session' ( 'switch-client -p' \ 'Switch to next session' ) 'switch-client -n' \ 'Switch to next session *' C-s 'switch-client -n' \ 'Switch to other session' '' 'command-prompt -T target { switch-client -t "%%" }' \ '' \ 'Convert current session to group (for independent sharing)' '' "new-session -A -s #{session_name}2 -t #{session_name} " \ 'Link the current window to another session' '' 'command-prompt -T target { link-window -t "%%" }' \ 'Unlink window from this session' C-u 'unlink-window' }
Wenn man regelmäßig mit Sessions arbeitet (v.a. mit mehr als zwei), dann bietet es sich an, das allgemeine Session-Menü zu überschreiben, um die in ~/.tmux.conf konfigurierten Sessions direkt aktivieren zu können (ich wähle als Auswahltaste den ersten Buchstaben des Session-Namens).