Bash: Zeigt den Exit-Status in der Eingabeaufforderung an:

11
GREEN="\e[1;32m"
RED="\e[1;31m"
NONE="\e[m"

get_exit_status(){
   es=$?
   if [ $es -eq 0 ]
   then
       echo -e "${GREEN}${es}${NONE}"
   else
       echo -e "${RED}${es}${NONE}"
   fi
}

get_path(){
    #dummy function
    echo "PATH"
}

PROMPT_COMMAND='exitStatus=$(get_exit_status)'

Folgendes gibt mir den richtigen exitStatus, aber Farbvariablen werden nicht erweitert:

PS1='${RED}\h $(get_path) ${exitStatus}${NONE} '

Der folgende gibt mir jedoch die Farben, aber der Exit-Status wird nicht aktualisiert:

PS1="${RED}\h $(get_path) ${exitStatus}${NONE} "

Was ist der richtige Weg, um dies zu tun? Wie kann ich das beheben, damit der exitStatus und die Farben funktionieren?

Dogbane
quelle

Antworten:

8

Gilles hat Ihr Hauptproblem identifiziert, aber ich wollte versuchen, es anders zu erklären.

Bash interpretiert das Escape der speziellen Eingabeaufforderung nur, bevor Variablen in der Eingabeaufforderung erweitert werden. Dies bedeutet, dass die Verwendung \ein einer Variablen, die über die Eingabeaufforderung erweitert wird, nicht funktioniert, obwohl sie direkt in funktioniert PS1.

Dies funktioniert beispielsweise wie erwartet und enthält roten Text:

PS1='\e[1;31m this is in red '

Dies ist jedoch nicht der Fall, sondern setzt nur ein Literal \ein die Eingabeaufforderung:

RED='\e[1;31m'
PS1="$RED not in red "

Wenn Sie die Farb-Escapezeichen in Variablen speichern möchten, können Sie mit ANSI-C-Anführungszeichen ( $'...') ein Literal-Escape-Zeichen in die Variable einfügen.

Dazu können Sie Ihre Definition von GREEN, REDund ändern NONE, sodass ihr Wert die tatsächliche Escape-Sequenz ist.

GREEN=$'\033[1;32m'
RED=$'\033[1;31m'
NONE=$'\033[m'

Wenn Sie das tun, sollte Ihr erstes PS1mit den einfachen Anführungszeichen funktionieren:

PS1='${RED}\h $(get_path) ${exitStatus}${NONE} '

Dann haben Sie jedoch ein zweites Problem.

Versuchen Sie, dies auszuführen, und drücken Sie Up Arrowdann Home, und Ihr Cursor springt nicht zum Zeilenanfang zurück.

Um dies zu beheben, ändern Sie die Farb-Escape-Sequenzen PS1in \[und \]um sie herum, z

PS1='\[${RED}\]\h $(get_path) $?\[${NONE}\] '

Sie können hier nicht get_exit_statusrichtig verwenden, da die Ausgabe sowohl druckende (den Exit-Code) als auch nicht druckbare Zeichen (die Farbcodes) enthält und es keine Möglichkeit gibt, sie in der Eingabeaufforderung korrekt zu markieren. Durch das Setzen wird \[...\]es als nicht vollständig gedruckt markiert, was nicht korrekt ist. Sie müssen die Funktion so ändern, dass nur der richtige Farbcode gedruckt wird, und ihn dann \[...\]in der Eingabeaufforderung umgeben.

Mikel
quelle
\[ist \1und \[ist \2. Diese entsprechen einer Readline- RL_PROMPT_{START,END}_IGNORESache, die sie auffordert, die Bytes beim Zählen der Eingabeaufforderungslänge auf dem Bildschirm zu ignorieren. Siehe lists.gnu.org/archive/html/bug-bash/2015-08/msg00027.html .
Arthur2e5
@ Arthur2e5 Meinst du \]ist \2? Und meinst du, deshalb wird es gebraucht ${exitStatus}? Mein Punkt war, dass ${exitStatus}es keine nicht druckbaren Zeichen enthält, daher sollte bash in der Lage sein, korrekt zu bestimmen, wie viele Zeichen die Eingabeaufforderung ohne das \[und \]in verschoben wird \[${exitStatus}\].
Mikel
Das Problem ist, dass es einige enthält - die Farben. (ANSI Escapes)
Arthur2e5
@ Arthur2e5 Ew, das habe ich total vermisst. :) Warum würdest du Farben setzen ... egal. :)
Mikel
1
"Bash ruft effektiv Echo auf deiner PS1 auf, nicht Echo -e" - nun, das ist falsch oder es fehlt einfach der Punkt. Bash erweitert Backslash-Escapezeichen wie \eund \033(und \[/ \], \uund \h) an der Eingabeaufforderung, nur bevor die Variablen erweitert werden. So PS1='\e[1;31m red'funktioniert, red='\e[1;31m'; PS1='$red red'tut es nicht.
Ilkkachu
3

Wenn Sie ausführen PS1='${RED}\h $(get_path) ${exitStatus}${NONE} ', wird die PS1Variable auf gesetzt ${RED}\h $(get_path) ${exitStatus}${NONE}, wobei nur \heine sofortige Escape-Sequenz angezeigt wird. Nachdem die Eingabeaufforderungssequenzen erweitert wurden (ergeben ${RED}darkstar $(get_path) ${exitStatus}${NONE}), führt die Shell die üblichen Erweiterungen durch, z. B. variable Erweiterungen. Sie erhalten eine angezeigte Eingabeaufforderung, die aussieht \e[1;31mdarkstar PATH 0\e[m. Nichts auf dem Weg erweitert die \eSequenzen zu tatsächlichen Escape-Zeichen.

Wenn Sie ausführen PS1="${RED}\h $(get_path) ${exitStatus}${NONE} ", wird die PS1Variable auf gesetzt \e[1;31m\h PATH 0\e[m. Die Variablen RED, exitStatusund NONEwerden zum Zeitpunkt der Zuordnung erweitert. Dann wird die Eingabeaufforderung enthält drei Aufforderung Escape - Sequenzen ( \e, \h, und \ewieder). Zu diesem Zeitpunkt müssen keine Shell-Variablen erweitert werden.

Um Farben zu sehen, benötigen Sie die Farbvariablen, um tatsächliche Escape-Zeichen zu enthalten. Sie können es so machen:

RED=$'\033[1;31m'
NONE=$'\033[m'
PS1='\[${RED}\]\h \w $?\[${NONE}\] '

$'…'Erweitert Backslash-Octal-Sequenzen und einige Backslash-Letter-Sequenzen wie \n, aber nicht einschließlich \e. Ich habe drei weitere Änderungen an Ihrer Eingabeaufforderung vorgenommen:

  • Verwenden Sie diese \[…\]Option für nicht druckbare Sequenzen, z. B. Befehle zum Ändern der Farbe. Andernfalls wird Ihre Anzeige verstümmelt, da bash die Breite der Eingabeaufforderung nicht ermitteln kann.
  • \w ist eine integrierte Escape-Sequenz zum Drucken des aktuellen Verzeichnisses.
  • Sie müssen nichts Kompliziertes $?in der Eingabeaufforderung anzeigen, wenn Sie überhaupt keine haben PROMPT_COMMAND.
Gilles 'SO - hör auf böse zu sein'
quelle
Ich denke, die Idee war, die Eingabeaufforderung beim Erfolg grün und beim Scheitern rot zu machen.
Mattdm
Ja, PS1ist falsch, aber der Ratschlag $'...'für REDund GREENsollte es mit Dogbane's funktionieren lassen PS1.
Mikel
1

Versuchen:

PS1='`exitStatus=$?;if [ $exitStatus -eq 0 ];then echo "\['${GREEN}'\]";else echo "\['${RED}'\]";fi;echo "\h $(get_path) ${exitStatus}${NONE}"`'
Shellholic
quelle
1
Danke, das funktioniert, aber gibt es eine Möglichkeit, dies zu erreichen, ohne eine if-Anweisung in die Eingabeaufforderung einbetten zu müssen?
Dogbane
1

Hier ist der Ansatz, den ich gewählt habe, er vermeidet die Verwendung von PROMPT_COMMAND.

# This function is called from a subshell in $PS1,
# to provide a colourised visual indicator of the exit status of the last run command
__COLOURISE_EXIT_STATUS() {
    # uncomment the next line for exit code output after each command, useful for debugging and testing
    #printf -- "\nexit code: $1\n" >&2
    [[ 0 == "$1" || 130 == "$1" ]] && printf -- "$GREEN" || printf -- "$RED"
}

Dann ist mein $PS1wie folgt:

PS1='# ${debian_chroot:+($debian_chroot)}'"${GREEN}\u${YELLOW}@${DARK_YELLOW}\h${WHITE}:${LIGHT_BLUE}\w${WHITE}\n"'\[$(__COLOURISE_EXIT_STATUS $?)\]# \$'"\[${WHITE}\] "
Kyle
quelle
1
Während es in diesem speziellen Fall keine Rolle spielt, da der einzige Wert $?eine Ganzzahl sein kann, sollten Sie printf '%b' "$GREEN"stattdessen wirklich verwenden . Vermeiden Sie außerdem die Verwendung von Funktionsnamen, denen ein Präfix vorangestellt ist __oder _die bei der Bash-Vervollständigung verwendet werden.
Nyuszika7h
1

Los geht's - Das funktioniert für mich (TM) in Ubuntu und anderen Linuxen (Linuxen?).

Der Grund für das Einfügen der Exit-Code-Erkennung $PS1besteht darin, dass für einen Host ein schreibgeschützter $PROMPT_COMMANDSatz festgelegt ist, bevor .bashrc gelesen wird.

l0b0
quelle
0

Denn PROMPT_COMMANDes ist sauberer, eine Funktion zu definieren und diese zu verwenden:

prompt_command() {
    # ...
}
PROMPT_COMMAND=prompt_command
nyuszika7h
quelle