Bash: afficher l'état de sortie à l'invite:

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

Ce qui suit me donne l'exitStatus correct mais les variables de couleur ne sont pas développées:

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

Cependant, celui ci-dessous, me donne les couleurs mais le statut de sortie ne se met pas à jour:

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

Quel est le bon moyen de le faire? Comment puis-je résoudre ce problème afin que l'exitStatus et les couleurs fonctionnent tous les deux?

dogbane
la source

Réponses:

8

Gilles a identifié votre problème principal, mais je voulais essayer de l'expliquer différemment.

Bash interprète l' invite spéciale s'échappe uniquement avant de développer les variables de l'invite. Cela signifie que l'utilisation \ed'une variable développée à partir de l'invite ne fonctionne pas, même si elle fonctionne directement dans PS1.

Par exemple, cela fonctionne comme prévu et donne du texte rouge:

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

Mais ce n'est pas le cas, cela met simplement un littéral \edans l'invite:

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

Si vous souhaitez stocker les échappements de couleur dans des variables, vous pouvez utiliser ANSI-C quoting ( $'...') pour mettre un caractère d'échappement littéral dans la variable.

Pour ce faire, vous pouvez changer votre définition GREEN, REDet NONE, de sorte que leur valeur est la séquence d'échappement réelle.

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

Si vous faites cela, votre premier PS1avec les guillemets simples devrait fonctionner:

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

Cependant, vous aurez alors un deuxième problème.

Essayez d'exécuter cela, puis appuyez sur Up Arrow, puis Home, et votre curseur ne reviendra pas au début de la ligne.

Pour résoudre ce problème, modifiez PS1pour inclure \[et \]autour des séquences d'échappement de couleur, par exemple

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

Vous ne pouvez pas l'utiliser get_exit_statuscorrectement ici, car sa sortie contient à la fois des caractères d'impression (le code de sortie) et des caractères non imprimables (les codes de couleur), et il n'y a aucun moyen de le marquer correctement dans l'invite. Le mettre \[...\]le marquerait comme non imprimable en entier, ce qui n'est pas correct. Vous devrez modifier la fonction afin qu'elle n'imprime que le bon code couleur, puis l'entourer \[...\]de l'invite.

Mikel
la source
\[est \1, et \[est \2. Celles-ci correspondent à quelque RL_PROMPT_{START,END}_IGNOREchose de readline qui lui demande d'ignorer les octets lors du comptage de la longueur de l'invite à l'écran. Voir lists.gnu.org/archive/html/bug-bash/2015-08/msg00027.html .
Arthur2e5
@ Arthur2e5 Voulez-vous dire \]est \2? Et voulez-vous dire que c'est pour cela qu'il est nécessaire ${exitStatus}? Mon point est que ${exitStatus}ne contient pas de caractères non-impression, si bash devrait être en mesure de déterminer correctement le nombre de caractères se déplace l'invite sans \[et \]dans \[${exitStatus}\].
Mikel
Le problème est qu'il en contient - les couleurs. (ANSI Escapes)
Arthur2e5
@ Arthur2e5 Ew, j'ai totalement raté ça. :) Pourquoi mettriez-vous des couleurs ... tant pis. :)
Mikel
1
"Bash appelle effectivement l'écho sur votre PS1, pas l'écho -e" - eh bien c'est faux ou il manque juste le point. Bash développe les échappements antislash comme \eet \033(et \[/ \],, \uet \h) à partir de l'invite, il le fait juste avant d' étendre les variables. Fonctionne donc PS1='\e[1;31m red', red='\e[1;31m'; PS1='$red red'non.
ilkkachu
3

Lorsque vous exécutez PS1='${RED}\h $(get_path) ${exitStatus}${NONE} ', la PS1variable est définie sur ${RED}\h $(get_path) ${exitStatus}${NONE}, où \hest uniquement une séquence d'échappement rapide. Une fois les séquences d'invites développées (cédant ${RED}darkstar $(get_path) ${exitStatus}${NONE}), le shell effectue les extensions habituelles telles que les extensions variables. Vous obtenez une invite affichée qui ressemble à \e[1;31mdarkstar PATH 0\e[m. Rien en cours de route n'étend les \eséquences en véritables personnages d'échappement.

Lorsque vous exécutez PS1="${RED}\h $(get_path) ${exitStatus}${NONE} ", la PS1variable est définie sur \e[1;31m\h PATH 0\e[m. Les variables RED, exitStatuset NONEsont étendues au moment de la cession. Ensuite , l'invite contient trois séquences d'échappement rapides ( \e, \het \eencore une fois). Il n'y a aucune variable shell à développer à ce stade.

Pour voir les couleurs, vous avez besoin que les variables de couleur contiennent des caractères d'échappement réels. Vous pouvez le faire de cette façon:

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

$'…'développe les séquences octales antislash et certaines séquences de lettres antislash telles que \n, mais sans les inclure \e. J'ai apporté trois autres modifications à votre invite:

  • À utiliser \[…\]autour de séquences non imprimables telles que les commandes de changement de couleur. Sinon, votre affichage finira par se brouiller car bash ne peut pas déterminer la largeur de l'invite.
  • \w est une séquence d'échappement intégrée pour imprimer le répertoire courant.
  • Vous n'avez besoin de rien de compliqué à afficher $?dans l'invite si vous n'en avez pas PROMPT_COMMANDen premier lieu.
Gilles 'SO- arrête d'être méchant'
la source
Je pense que l'idée était de faire en sorte que l'invite soit verte en cas de succès et rouge en cas d'échec.
mattdm
Oui, PS1est faux, mais les conseils d'utilisation $'...'pour REDet GREENce devrait le faire fonctionner à l' aide dogbane PS1.
Mikel
1

Essayer:

PS1='`exitStatus=$?;if [ $exitStatus -eq 0 ];then echo "\['${GREEN}'\]";else echo "\['${RED}'\]";fi;echo "\h $(get_path) ${exitStatus}${NONE}"`'
shellholic
la source
1
Merci, cela fonctionne, mais existe-t-il un moyen d'y parvenir sans avoir à incorporer une instruction if dans l'invite?
Dogbane
1

Voici l'approche que j'ai choisie, elle évite l'utilisation de 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"
}

Alors mon $PS1est comme suit:

PS1='# ${debian_chroot:+($debian_chroot)}'"${GREEN}\u${YELLOW}@${DARK_YELLOW}\h${WHITE}:${LIGHT_BLUE}\w${WHITE}\n"'\[$(__COLOURISE_EXIT_STATUS $?)\]# \$'"\[${WHITE}\] "
Kyle
la source
1
Bien que cela n'ait pas d'importance dans ce cas particulier, comme la seule valeur que $?peut avoir est un entier, vous devriez vraiment l'utiliser à la printf '%b' "$GREEN"place. Évitez également d'utiliser des noms de fonction préfixés par __ou _tels qu'ils sont utilisés par bash-complét.
nyuszika7h
1

C'est parti - Cela fonctionne pour moi (TM) dans Ubuntu et d'autres Linux (Linuxen?).

La raison de la détection de code de sortie $PS1est qu'un hôte a un ensemble en lecture seule $PROMPT_COMMANDavant la lecture de .bashrc.

l0b0
la source
0

Pour PROMPT_COMMAND, il est plus propre de définir une fonction et de l'utiliser:

prompt_command() {
    # ...
}
PROMPT_COMMAND=prompt_command
nyuszika7h
la source