Bash: visualizza lo stato di uscita nel prompt:


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)'

Quanto segue mi dà lo exitStatus corretto ma le variabili di colore non sono espanse:

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

Tuttavia, quello qui sotto, mi dà i colori ma lo stato di uscita non si aggiorna:

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

Qual è il modo giusto per farlo? Come posso risolvere questo problema in modo che exitStatus e i colori funzionino entrambi?

Risposte:


8

Gilles ha identificato il tuo problema principale, ma volevo provare a spiegarlo diversamente.

Bash interpreta il escape speciale del prompt solo prima di espandere qualsiasi variabile nel prompt. Ciò significa che l'utilizzo \ein una variabile che viene espansa dal prompt non funziona, anche se funziona direttamente in PS1.

Ad esempio, funziona come previsto e fornisce un testo rosso:

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

Ma non è così, mette semplicemente un messaggio \enel prompt:

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

Se si desidera memorizzare le fughe di colore in variabili, è possibile utilizzare le virgolette ANSI-C ( $'...') per inserire un carattere di escape letterale nella variabile.

Per fare questo, è possibile cambiare la definizione di GREEN, REDe NONE, quindi il loro valore è la sequenza di escape vero e proprio.

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

Se lo fai, il tuo primo PS1con le virgolette singole dovrebbe funzionare:

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

Tuttavia, avrai un secondo problema.

Prova a eseguirlo, quindi premi Up Arrow, quindi Homeil cursore non tornerà all'inizio della riga.

Per risolvere questo problema, cambia PS1per includere \[e \]attorno alle sequenze di escape del colore, ad es

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

Non è possibile utilizzare get_exit_statuscorrettamente qui, poiché il suo output contiene sia caratteri di stampa (il codice di uscita) sia caratteri non stampabili (i codici di colore), e non c'è modo di contrassegnarlo correttamente nel prompt. Mettere \[...\]lo contrassegnerebbe come non stampabile per intero, il che non è corretto. Dovrai modificare la funzione in modo che stampi solo il codice colore corretto, quindi racchiudilo \[...\]nel prompt.


\[è \1ed \[è \2. Quelli da corrispondono a qualcosa di readline RL_PROMPT_{START,END}_IGNOREche gli chiede di ignorare i byte quando si conteggia la lunghezza del prompt sullo schermo. Vedi lists.gnu.org/archive/html/bug-bash/2015-08/msg00027.html .
Arthur2e5,

@ Arthur2e5 Vuoi dire \]è \2? E vuoi dire che è per questo che è necessario ${exitStatus}? Il mio punto era che ${exitStatus}non contiene caratteri non stampabili, quindi bash dovrebbe essere in grado di determinare correttamente quanti caratteri sposta il prompt senza \[e \]in \[${exitStatus}\].
Mikel,

Il problema è che contiene alcuni - i colori. (ANSI Escapes)
Arthur2e5,

@ Arthur2e5 Ew, mi mancava del tutto. :) Perché dovresti mettere i colori ... non importa. :)
Mikel,

1
"Bash sta effettivamente chiamando echo sulla tua PS1, non echo -e" - beh, è ​​sbagliato o manca solo il punto. Bash espande backslash-escape come \ee \033(e \[/ \], \ue \h) dal prompt, lo fa solo prima di espandere le variabili. Quindi PS1='\e[1;31m red'funziona, red='\e[1;31m'; PS1='$red red'no.
ilkkachu,

3

Quando si esegue PS1='${RED}\h $(get_path) ${exitStatus}${NONE} ', la PS1variabile è impostata su ${RED}\h $(get_path) ${exitStatus}${NONE}, dove \hè solo una rapida sequenza di escape. Dopo l'espansione (resa ${RED}darkstar $(get_path) ${exitStatus}${NONE}) delle sequenze di prompt , la shell esegue le solite espansioni come le espansioni variabili. Viene visualizzato un prompt che appare come \e[1;31mdarkstar PATH 0\e[m. Niente lungo il percorso espande le \esequenze ai veri personaggi di fuga.

Quando si esegue PS1="${RED}\h $(get_path) ${exitStatus}${NONE} ", la PS1variabile è impostata su \e[1;31m\h PATH 0\e[m. Le variabili RED, exitStatuse NONEvengono espansi al momento della cessione. Poi la richiesta contiene tre sequenze di escape rapide ( \e, \he \edi nuovo). Non ci sono variabili shell da espandere in questa fase.

Per visualizzare i colori, è necessario che le variabili dei colori contengano caratteri di escape effettivi. Puoi farlo in questo modo:

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

$'…'espande sequenze barra rovesciata e alcune sequenze lettere rovesciate come \n, ma non incluse \e. Ho apportato altre tre modifiche al tuo prompt:

  • Utilizzare \[…\]attorno a sequenze non stampabili come comandi di cambio colore. Altrimenti il ​​tuo schermo si confonderà perché bash non riesce a capire la larghezza del prompt.
  • \w è una sequenza di escape integrata per stampare la directory corrente.
  • Non è necessario nulla di complicato da mostrare $?nel prompt se PROMPT_COMMANDin primo luogo non si ha a.

Penso che l'idea fosse quella di rendere il prompt verde per il successo e rosso per il fallimento.
Mattdm

Sì, PS1è sbagliato, ma i consigli da usare $'...'per REDe GREENdovrebbero farlo funzionare usando i dogbane PS1.
Mikel

1

Provare:

PS1='`exitStatus=$?;if [ $exitStatus -eq 0 ];then echo "\['${GREEN}'\]";else echo "\['${RED}'\]";fi;echo "\h $(get_path) ${exitStatus}${NONE}"`'

1
Grazie, funziona, ma c'è un modo per farlo senza dover incorporare un'istruzione if nel prompt?
dogbane

1

Ecco l'approccio con cui sono andato, evita l'uso di 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"
}

Quindi il mio $PS1è il seguente:

PS1='# ${debian_chroot:+($debian_chroot)}'"${GREEN}\u${YELLOW}@${DARK_YELLOW}\h${WHITE}:${LIGHT_BLUE}\w${WHITE}\n"'\[$(__COLOURISE_EXIT_STATUS $?)\]# \$'"\[${WHITE}\] "

1
Anche se non importa in questo caso particolare, poiché l'unico valore che $?può avere è un numero intero, dovresti printf '%b' "$GREEN"invece usarlo . Inoltre, evitare di usare nomi di funzioni con il prefisso __o _come vengono usati da bash-completamento.
nyuszika7h

1

Ecco qua - This Works For Me (TM) in Ubuntu e altri Linux (Linuxen?).

Il motivo per cui $PS1viene $PROMPT_COMMANDinserito il rilevamento del codice di uscita è che un host ha un set di sola lettura prima di leggere .bashrc.


0

Per PROMPT_COMMAND, è più pulito definire una funzione e usare quella:

prompt_command() {
    # ...
}
PROMPT_COMMAND=prompt_command
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.