zur Übersicht: Linux: meine Software und Konfigurationstipps

Shellprompt-Konfiguration (bash)

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.

Warum sollte man den Standard ändern wollen?

Der Standardprompt sieht bei openSUSE so aus:

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:

Änderungsmöglichkeiten

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
\aan ASCII bell character (07)
\dthe 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
\ean ASCII escape character (033)
\hthe hostname up to the first `.'
\Hthe hostname
\jthe number of jobs currently managed by the shell
\lthe basename of the shell's terminal device name
\nnewline
\rcarriage return
\sthe name of the shell, the basename of $0 (the portion following the final slash)
\tthe current time in 24-hour HH:MM:SS format
\Tthe current time in 12-hour HH:MM:SS format
\@the current time in 12-hour am/pm format
\Athe current time in 24-hour HH:MM format
\uthe username of the current user
\vthe version of bash (e.g., 2.00)
\Vthe release of bash, version + patch level (e.g., 2.00.0)
\wthe current working directory, with $HOME abbreviated with a tilde
\Wthe 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 $
\nnn 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:

alter Prompt
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>
neuer Prompt
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:>

mehrzeile Eingabe

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:

alte Version von PS2
hl@inno:~/tmp> echo 'foo
> bar'
foo
bar
hl@inno:~/tmp>
neue Version von PS2
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:

alte Version, normaler User
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>
alte Version, Superuser
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:~ #
neue Version, normaler User
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:>
neue Version, Superuser
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.

Ich will auch!!!

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.

normaler User
oldPS1=$PS1
oldPS2=$PS2
PS1='\nec:$(printf %-3d $?) \t  \u@\h:\w\nstart cmd:> '
PS2='cont. cmd:> '
Superuser
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.

Es geht noch besser

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.

normaler User
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:> "
Superuser
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:>