Bash: muestra el estado de salida en el indicador:


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

Lo siguiente me da el estado de salida correcto pero las variables de color no se expanden:

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

Sin embargo, el siguiente me da los colores pero el estado de salida no se actualiza:

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

¿Cuál es la forma correcta de hacer esto? ¿Cómo puedo solucionar esto para que el exitStatus y los colores funcionen?

Respuestas:


8

Gilles identificó su problema principal, pero quería intentar explicarlo de manera diferente.

Bash interpreta los escapes de solicitud especiales solo antes de expandir cualquier variable en la solicitud. Esto significa que el uso \ede una variable que se expande desde el indicador no funciona, aunque sí funciona directamente PS1.

Por ejemplo, esto funciona como se esperaba y da texto rojo:

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

Pero esto no es así, solo pone un literal \een el indicador:

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

Si desea almacenar los escapes de color en las variables, puede usar la cita ANSI-C ( $'...') para poner un carácter de escape literal en la variable.

Para ello, puede cambiar su definición de GREEN, REDy NONE, por lo que su valor es la secuencia de escape real.

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

Si haces eso, tu primero PS1con las comillas simples debería funcionar:

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

Sin embargo, entonces tendrás un segundo problema.

Intente ejecutar eso, luego presione Up Arrow, Homey su cursor no volverá al inicio de la línea.

Para solucionarlo, cambie PS1para incluir \[y \]alrededor de las secuencias de escape de color, p. Ej.

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

No se puede usar get_exit_statuscorrectamente aquí, ya que su salida contiene caracteres impresos (el código de salida) y no impresos (los códigos de color), y no hay forma de marcarlo correctamente en el mensaje. Ponerlo \[...\]lo marcaría como no impreso en su totalidad, lo cual no es correcto. Tendrá que cambiar la función para que solo imprima el código de color adecuado y luego rodearlo \[...\]en el indicador.


\[es \1, y \[es \2. Los que corresponden a algo de readline RL_PROMPT_{START,END}_IGNOREque le pide que ignore los bytes al contar la longitud del indicador en la pantalla. Ver lists.gnu.org/archive/html/bug-bash/2015-08/msg00027.html .
Arthur2e5

@ Arthur2e5 ¿Quieres decir que \]es \2? ¿Y quieres decir que por eso es necesario ${exitStatus}? Mi punto era que ${exitStatus}no contiene caracteres que no se imprimen, por lo que bash debería poder determinar correctamente cuántos caracteres mueve la solicitud sin el \[y \]en \[${exitStatus}\].
Mikel

El problema es que contiene algunos: los colores. (ANSI escapa)
Arthur2e5

@ Arthur2e5 Ew, lo extrañé por completo. :) ¿Por qué pondrías colores ... no importa. :)
Mikel

1
"Bash está efectivamente llamando a echo en tu PS1, no echo -e" - bueno, eso está mal o simplemente está perdiendo el punto. Bash expande los escapes de barra invertida como \ey \033(y \[/ \], \uy \h) desde el indicador, solo lo hace antes de expandir las variables. Entonces PS1='\e[1;31m red'funciona, red='\e[1;31m'; PS1='$red red'no.
ilkkachu

3

Cuando ejecuta PS1='${RED}\h $(get_path) ${exitStatus}${NONE} ', la PS1variable se establece en ${RED}\h $(get_path) ${exitStatus}${NONE}, donde solo \hhay una secuencia de escape rápida. Después de expandir las secuencias de solicitud (rendimiento ${RED}darkstar $(get_path) ${exitStatus}${NONE}), el shell realiza las expansiones habituales, como las expansiones variables. Aparece un mensaje que se muestra como este \e[1;31mdarkstar PATH 0\e[m. Nada en el camino expande las \esecuencias a personajes de escape reales.

Cuando ejecuta PS1="${RED}\h $(get_path) ${exitStatus}${NONE} ", la PS1variable se establece en \e[1;31m\h PATH 0\e[m. Las variables RED, exitStatusy NONEse expanden en el momento de la asignación. Entonces el indicador contiene tres secuencias de escape rápidas ( \e, \hy \ede nuevo). No hay variables de shell para expandir en esta etapa.

Para ver los colores, necesita que las variables de color contengan caracteres de escape reales. Puedes hacerlo de esta manera:

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

$'…'expande las secuencias de barra diagonal inversa y algunas secuencias de barra diagonal inversa como \n, pero no incluidas \e. Hice otros tres cambios a su solicitud:

  • Se usa \[…\]alrededor de secuencias que no se imprimen, como los comandos de cambio de color. De lo contrario, su pantalla terminará confundida porque bash no puede calcular el ancho de la solicitud.
  • \w es una secuencia de escape integrada para imprimir el directorio actual.
  • No necesita nada complicado para mostrar $?en el indicador si no tiene uno PROMPT_COMMANDen primer lugar.

Creo que la idea era hacer que el mensaje fuera verde en el éxito y rojo en el fracaso.
mattdm

Sí, PS1está mal, pero el consejo para el uso $'...'de REDy GREENdebe hacer que funcione usando las adelfas de PS1.
Mikel

1

Tratar:

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

1
Gracias, esto funciona, pero ¿hay alguna manera de lograr esto sin tener que incrustar una declaración if dentro del indicador?
dogbane

1

Aquí está el enfoque con el que he seguido, evita el uso 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"
}

Entonces mi $PS1es el siguiente:

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

1
Si bien no importa en este caso particular, ya que el único valor que $?puede tener es un número entero, en realidad debería usarlo printf '%b' "$GREEN". Además, evite usar nombres de funciones con el prefijo __o _tal como los usa bash-complete.
nyuszika7h

1

Aquí tienes : esto funciona para mí (TM) en Ubuntu y otras Linux (¿Linuxen?).

La razón para poner la detección del código de salida $PS1es que un host tiene un $PROMPT_COMMANDconjunto de solo lectura antes de que se lea .bashrc.


0

Para PROMPT_COMMAND, es más claro definir una función y usar eso:

prompt_command() {
    # ...
}
PROMPT_COMMAND=prompt_command
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.