zur Übersicht: Linux: meine Software und Konfigurationstipps
Seit vielen Jahren weiß ich, dass man den Shellpromt konfigurieren kann. Aber so richtig interessiert hat mich das nie; ich habe daran nie etwas geändert.
Dass ich mich nun doch damit befasst habe, ist zwei konkreten Ärgernissen zu verdanken. Die folgenden Aussagen beziehen sich auf die bash.
Der Standardprompt sieht bei openSUSE so aus:
für normale User: username@hostname:pfad #
, z.B. hl@inno:~>
für den Superuser (root): hostname:pfad>
, z.B. inno:~ #
Die Kombination Schwarz-Grün ist natürlich nicht Standard, das ist meine persönliche Vorliebe, für die ich an dieser Stelle mal ein bisschen Werbung gemacht haben wollte.
Folgende, voneinander völlig unabhängige Aspekte haben mich immer mal wieder gestört:
Wenn der aktuelle Pfad mal etwas länger wird, hat man weniger Platz für die Kommandozeile, bevor sie umbricht. Und wenn man zwischen Verzeichnissen wechselt, werden Kommandos schwerer vergleichbar, wenn sie nicht senkrecht untereinander stehen.
Ich brauche oft den exit code eines Kommandos. echo $?
ist zwar schnell getippt, aber auch das geht einem irgendwann auf die Nerven. Aus test -f "$filepath"
wird dann immer gleich test -f "$filepath" && echo ja || echo nein
. Klar, den Rattenschwanz muss man nur einmal tippen, aber trotzdem.
Ich starte ein lange laufendes Kommando, interessiere mich dafür, wie lange es braucht – und merke dann, dass ich time
vergessen habe.
Das Aussehen des normalen Prompts bestimmt der Inhalt der Variablen PS1 und PS2. Die kann man setzen, wie jede andere Variable auch: PS1="kein Prompt "
:-)
Auszug aus man bash; diese Escapesequenzen kann man in den Prompt einbauen:
Escapesequenz | Bedeutung |
---|---|
\a | an ASCII bell character (07) |
\d | the date in "Weekday Month Date" format (e.g., "Tue May 26") |
\D{format} | the format is passed to strftime(3) and the result is inserted into the prompt string; an empty format results in a locale-specific time representation. The braces are required |
\e | an ASCII escape character (033) |
\h | the hostname up to the first `.' |
\H | the hostname |
\j | the number of jobs currently managed by the shell |
\l | the basename of the shell's terminal device name |
\n | newline |
\r | carriage return |
\s | the name of the shell, the basename of $0 (the portion following the final slash) |
\t | the current time in 24-hour HH:MM:SS format |
\T | the current time in 12-hour HH:MM:SS format |
\@ | the current time in 12-hour am/pm format |
\A | the current time in 24-hour HH:MM format |
\u | the username of the current user |
\v | the version of bash (e.g., 2.00) |
\V | the release of bash, version + patch level (e.g., 2.00.0) |
\w | the current working directory, with $HOME abbreviated with a tilde |
\W | the basename of the current working directory, with $HOME abbreviated with a tilde |
\! | the history number of this command |
\# | the command number of this command |
\$ | if the effective UID is 0, a #, otherwise a $ |
\n | nn the character corresponding to the octal number nnn |
\\ | a backslash |
\[ | begin a sequence of non-printing characters, which could be used to embed a terminal control sequence into the prompt |
\] | end a sequence of non-printing characters |
Ich verwende einen mehrzeiligen Prompt. Die erste Zeile ist leer (schafft also nur Abstand zur Kommandoausgabe); das hat vor allem ästhetischen Wert, verhindert aber auch, dass Ausgaben von Kommandos übersehen werden, die nicht auf newline (\n) enden. In der zweiten Zeile stehen der exit code des letzten Kommandos, die Uhrzeit (im Moment der Erzeugung des Prompts) und der Pfad des aktuellen Verzeichnisses. In der dritten Zeile steht dann die eigentliche Eingabeaufforderung:
hl@inno:~/tmp> echo foo foo hl@inno:~/tmp> echo -n foo foohl@inno:~/tmp> ls nichtda ls: Zugriff auf nichtda nicht möglich: Datei oder Verzeichnis nicht gefunden hl@inno:~/tmp>
ec:0 02:41:14 hl@inno:~/tmp start cmd:> echo foo foo ec:0 02:41:22 hl@inno:~/tmp start cmd:> echo -n foo foo ec:0 02:41:26 hl@inno:~/tmp start cmd:> ls nichtda ls: Zugriff auf nichtda nicht möglich: Datei oder Verzeichnis nicht gefunden ec:2 02:41:30 hl@inno:~/tmp start cmd:>
Wenn ein Kommando über mehrere Zeilen geht (nicht in dem Sinn, dass es wegen zu vieler Zeichen am Zeilenende umbrochen wird, sondern durch Eingabe von \n (Taste <Enter>), ohne dass die Kommandoeingabe dadurch beendet wird, weil dieses \n maskiert wird (von \, ' oder ")), verwendet die bash einen anderen Prompt, um diesen Umstand zu verdeutlichen. Ich habe die Prompts so abgeglichen, dass sie dieselbe Länge haben und selbsterklärend sind:
hl@inno:~/tmp> echo 'foo > bar' foo bar hl@inno:~/tmp>
ec:0 02:50:05 hl@inno:~/tmp start cmd:> echo 'foo cont. cmd:> bar' foo bar ec:0 03:01:44 hl@inno:~/tmp start cmd:>
Die Konfiguration des eigenen Prompts kann man sich übrigens ganz einfach ansehen:
hl@inno:~/tmp> echo "$PS1" $(ppwd \l)\u@\h:\w> hl@inno:~/tmp> echo "$PS1" | hexdump -C 00000000 24 28 70 70 77 64 20 5c 6c 29 5c 75 40 5c 68 3a |$(ppwd \l)\u@\h:| 00000010 5c 77 3e 20 0a |\w> .| 00000015 hl@inno:~/tmp>
inno:~ # echo "$PS1" \[\]\h:\w # \[\] inno:~ # echo "$PS1" | hexdump -C 00000000 5c 5b 1b 5b 31 6d 1b 5b 33 31 6d 5c 5d 5c 68 3a |\[.[1m.[31m\]\h:| 00000010 5c 77 20 23 20 5c 5b 1b 28 42 1b 5b 6d 5c 5d 0a |\w # \[.(B.[m\].| 00000020 inno:~ #
ec:0 03:13:41 hl@inno:~ start cmd:> echo "$PS1" \nec:$(printf %-3d $?) \t \u@\h:\w\nstart cmd:> ec:0 03:13:44 hl@inno:~ start cmd:> echo "$PS1" | hexdump -C 00000000 5c 6e 65 63 3a 24 28 70 72 69 6e 74 66 20 25 2d |\nec:$(printf %-| 00000010 33 64 20 24 3f 29 20 5c 74 20 20 5c 75 40 5c 68 |3d $?) \t \u@\h| 00000020 3a 5c 77 5c 6e 73 74 61 72 74 20 63 6d 64 3a 3e |:\w\nstart cmd:>| 00000030 20 0a | .| 00000032 ec:0 03:13:49 hl@inno:~ start cmd:>
ec:0 03:14:45 root@inno:~ start cmd: # echo "$PS1" \nec:$(printf %-3d $?) \t \u@\h:\w\n\[\]start cmd: # \[\] ec:0 03:15:16 root@inno:~ start cmd: # echo "$PS1" | hexdump -C 00000000 5c 6e 65 63 3a 24 28 70 72 69 6e 74 66 20 25 2d |\nec:$(printf %-| 00000010 33 64 20 24 3f 29 20 5c 74 20 20 5c 75 40 5c 68 |3d $?) \t \u@\h| 00000020 3a 5c 77 5c 6e 5c 5b 1b 5b 31 6d 1b 5b 33 31 6d |:\w\n\[.[1m.[31m| 00000030 5c 5d 73 74 61 72 74 20 63 6d 64 3a 20 23 20 5c |\]start cmd: # \| 00000040 5b 1b 28 42 1b 5b 6d 5c 5d 0a |[.(B.[m\].| 0000004a ec:0 03:15:18 root@inno:~ start cmd: #
Die teilweise rote Ausgabe des Variableninhalts für den Superuser erklärt sich dadurch, dass die Variable Steuersequenzen für das Terminal erhält, die nicht direkt angezeigt werden, sich aber auswirken. Wenn man die ausgabe von echo nicht direkt ausgibt, sondern in ein Programm wie hexdump (alternativ: od -c -t x1
) schiebt, dann sieht man, was wirklich passiert. Die Punkte in der Textausgabe von hexdump stehen für nicht druckbare Zeichen. Der Punkt am Ende (0A) steht für das newline, mit dem echo jede ausgabe beendet. Nimmt man statt echo
den Aufruf echo -n
, dann fällt das abschließende 0A weg. Das entscheidende Zeichen ist ESC (Escape: dezimal 27, hexadezimal 1b, oktal 033); dieses Zeichen ist nicht druckbar und nimmt die Terminalprogrammierung vor.
Was $(ppwd \l)
macht(e), weiß ich nicht. Das ist wohl irgendein SuSE-Überbleibsel. Jedenfalls kennt die Shell dieses Kommando nicht (vielleicht habe ich das irgendwie kaputt gekriegt, wer weiß), und es produziert (dementsprechend) keine sichtbare Ausgabe.
Freut mich. Und vor allem: Das ist ganz einfach. Man muss nur die Variablen $PS1 und $PS2 geeignet überschreiben. Ich mache das in der Datei ~/.alias, auch wenn die dafür nicht gedacht ist. Das sollte wohl eher in ~.bashrc. Der geneigte Leser muss meinem schlechten Beispiel nicht folgen. Solange man aber nur bash benutzt, macht das keinen Unterschied.
oldPS1=$PS1 oldPS2=$PS2 PS1='\nec:$(printf %-3d $?) \t \u@\h:\w\nstart cmd:> ' PS2='cont. cmd:> '
oldPS1=$PS1 oldPS2=$PS2 PS1='\nec:$(printf %-3d $?) \t \u@\h:\w\n\['$'\033[1m\033[31m\\]start cmd: # \[\033(B\033[m\\]' PS2=$'\[\033[1m\033[31m\\]cont. cmd: # \[\033(B\033[m\\]'
Dass ich die vorigen Werte in $oldPS1 und $oldPS2 sichere, ist für die Darstellung natürlich irrelevant. Aber so kann man am einfachsten rumprobieren. Mit PS1=$oldPS1
ist der alte Zustand wiederhergestellt, und man kann dann aus einer übersichtlichen Situation heraus PS1 mit eigenen Werten belegen.
Ich habe festgestellt, dass es doch ganz nett ist, wenn einem exit codes ungleich null rot angezeigt werden. Das ist kein großes Problem: Man prüft einfach in dem $()-Kommando, wie der exit code aussieht und reagiert entsprechend. Was mich bisher etwas verwirrt ist, dass man innerhalb von $() die Escapesequenzen für Terminal-Steuercodes nicht ausgeben darf.
oldPS1=$PS1 oldPS2=$PS2 TERM_RED_START=$'\033[1m\033[31m' TERM_RED_END=$'\033(B\033[m' PS1='\nec:$(ec=$?; if [ 0 -eq $ec ]; then printf %-3d $ec; else echo -n "$TERM_RED_START"; printf %-3d $ec; echo "$TERM_RED_END"; fi) \t \u@\h:\w\nstart cmd:> ' PS2="cont. cmd:> "
oldPS1=$PS1 oldPS2=$PS2 TERM_RED_START=$'\033[1m\033[31m' TERM_RED_END=$'\033(B\033[m' PS1='\nec:$(ec=$?; if [ 0 -eq $ec ]; then printf %-3d $ec; else echo -n "$TERM_RED_START"; printf %-3d $ec; echo "$TERM_RED_END"; fi) \t \u@\h:\w\n\['$'\033[1m\033[31m\\]start cmd: # \[\033(B\033[m\\]' PS2="\[${TERM_RED_START}\]cont. cmd: # \[\033(B\033[m\]"
Das sieht dann so aus:
ec:0 07:24:45 hl@inno:~
start cmd:> true
ec:0 07:24:46 hl@inno:~
start cmd:> false
ec:1 07:24:50 hl@inno:~
start cmd:>