Alinear a la derecha parte de la solicitud

27

Estoy seguro de que he visto a alguien alinear una parte de su mensaje a la derecha en su ventana de terminal y luego hacer que el cursor real comience en una segunda línea. Sé que puedo lograr la segunda línea con un "\ n" en la PS1, pero no puedo entender cómo alinear parte de ella a la derecha. ¿Lo que vi fue solo un espacio en blanco agregado entre las dos cadenas?

Felix Andersen
fuente

Respuestas:

17

Lo que desea se puede hacer fácilmente mostrando la primera línea antes de mostrar la solicitud. Por ejemplo, lo siguiente muestra un aviso \wa la izquierda de la primera línea y un aviso \u@\ha la derecha de la primera línea. Utiliza la $COLUMNSvariable que contiene el ancho del terminal y el $PROMPT_COMMANDparámetro que se evalúa antes de que bash muestre el indicador.

print_pre_prompt () 
{ 
    PS1L=$PWD
    if [[ $PS1L/ = "$HOME"/* ]]; then PS1L=\~${PS1L#$HOME}; fi
    PS1R=$USER@$HOSTNAME
    printf "%s%$(($COLUMNS-${#PS1L}))s" "$PS1L" "$PS1R"
}
PROMPT_COMMAND=print_pre_prompt
Gilles 'SO- deja de ser malvado'
fuente
3
Tenga en cuenta que las cosas se vuelven mucho más complicadas si desea una solicitud de color a la izquierda, ya que los caracteres que no se imprimen significan que la longitud de la cadena no es la misma que la cantidad de caracteres mostrados.
Mu Mind
1
Tanto esta como la respuesta más votada no funcionan correctamente si .inputrces así set show-mode-in-prompt on. Ambos no calculan la longitud de los códigos ANSI CSI no prinables , y no los encierran correctamente \[y \]como lo menciona @Mu Mind. Ver esta respuesta para una resolución.
Tom Hale
26

Con base en la información que encontré aquí, pude descubrir una solución más simple para alinear a la derecha mientras acomodaba contenido de longitud variable a la derecha o izquierda, incluido el soporte para el color. Agregado aquí para su conveniencia ...

Nota sobre los colores: usar el \033escape en favor de alternativas, sin \[\]agrupaciones, resulta más compatible y por lo tanto recomendado.

El truco es escribir primero el lado derecho, luego usar el retorno de carro ( \r) para volver al inicio de la línea y continuar sobrescribiendo el contenido del lado izquierdo encima de eso, de la siguiente manera:

prompt() {
    PS1=$(printf "%*s\r%s\n\$ " "$(tput cols)" 'right' 'left')
}
PROMPT_COMMAND=prompt

Estoy usando tput colsMac OS X para recuperar el ancho de terminal / consola terminfoya que mi $COLUMNSvar no está incluida envpero puede sustituir el " *" valor reemplazable %*s, proporcionando " ${COLUMNS}", o cualquier otro valor que prefiera.

El siguiente ejemplo se utiliza $RANDOMpara generar contenido de diferente longitud que incluye colores y muestra cómo puede extraer funciones para refactorizar la implementación a funciones reutilizables.

function prompt_right() {
  echo -e "\033[0;36m$(echo ${RANDOM})\033[0m"
}

function prompt_left() {
  echo -e "\033[0;35m${RANDOM}\033[0m"
}

function prompt() {
    compensate=11
    PS1=$(printf "%*s\r%s\n\$ " "$(($(tput cols)+${compensate}))" "$(prompt_right)" "$(prompt_left)")
}
PROMPT_COMMAND=prompt

Dado que printfsupone que la longitud de la cadena es el número de caracteres que necesitamos para compensar la cantidad de caracteres necesarios para representar los colores, siempre se encontrará cerca del final de la pantalla debido a los caracteres ANSI no impresos sin compensación. Los caracteres requeridos para el color permanecen constantes y encontrará que también printf tiene en cuenta el cambio de longitud, tal como lo devuelve por $RANDOMejemplo ', que mantiene intacta nuestra alineación correcta.

Este no es el caso con las secuencias de escape pronta especial de bash (es decir. \u, \w, \h, \t), Sin embargo, ya que éstas sólo registrará una longitud de 2 a causa fiesta sólo se traducirá ellos cuando aparece el indicador, después de printf ha hecho que la cadena. Esto no afecta el lado izquierdo, pero es mejor evitarlos a la derecha.

Sin embargo, no tiene consecuencias si el contenido generado permanecerá en una longitud constante. Al igual que con la \topción de tiempo que siempre representará la misma cantidad de caracteres (8) durante 24 veces. Solo necesitamos tener en cuenta la compensación requerida para dar cabida a la diferencia entre 2 caracteres contados, lo que resulta en 8 caracteres cuando se imprime, en estos casos.

Tenga en cuenta que es posible que deba escapar tres veces de \\\algunas secuencias de escape que, de lo contrario, tienen significado para las cadenas. Al igual que con el siguiente ejemplo, el escape del directorio de trabajo actual \wno tiene ningún significado, por lo que funciona como se esperaba, pero el tiempo \t, lo que significa un carácter de tabulación, no funciona como se espera sin que se escape primero primero.

function prompt_right() {
  echo -e "\033[0;36m\\\t\033[0m"
}

function prompt_left() {
  echo -e "\033[0;35m\w\033[0m"
}

function prompt() {
    compensate=5
    PS1=$(printf "%*s\r%s\n\$ " "$(($(tput cols)+${compensate}))" "$(prompt_right)" "$(prompt_left)")
}
PROMPT_COMMAND=prompt

nJoy!

nickl-
fuente
8

Usar printfcon $COLUMNSfuncionó muy bien, algo como:

printf "%${COLUMNS}s\n" "hello"

Lo justificó perfectamente para mí.

jmervine
fuente
6

Lo siguiente pondrá la fecha y hora actual en ROJO en el RHS de la terminal.

# Create a string like:  "[ Apr 25 16:06 ]" with time in RED.
printf -v PS1RHS "\e[0m[ \e[0;1;31m%(%b %d %H:%M)T \e[0m]" -1 # -1 is current time

# Strip ANSI commands before counting length
# From: https://www.commandlinefu.com/commands/view/12043/remove-color-special-escape-ansi-codes-from-text-with-sed
PS1RHS_stripped=$(sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" <<<"$PS1RHS")

# Reference: https://en.wikipedia.org/wiki/ANSI_escape_code
local Save='\e[s' # Save cursor position
local Rest='\e[u' # Restore cursor to save point

# Save cursor position, jump to right hand edge, then go left N columns where
# N is the length of the printable RHS string. Print the RHS string, then
# return to the saved position and print the LHS prompt.

# Note: "\[" and "\]" are used so that bash can calculate the number of
# printed characters so that the prompt doesn't do strange things when
# editing the entered text.

PS1="\[${Save}\e[${COLUMNS:-$(tput cols)}C\e[${#PS1RHS_stripped}D${PS1RHS}${Rest}\]${PS1}"

Ventajas:

  • Funciona correctamente con colores y códigos ANSI CSI en el indicador RHS
  • Sin subprocesos. shellchecklimpiar.
  • Funciona correctamente si .inputrctiene set show-mode-in-prompt on.
  • Encapsula correctamente los caracteres que no proporcionan la longitud de la solicitud \[y, \]por lo tanto, la edición del texto ingresado en la solicitud no hace que la solicitud se vuelva a imprimir de manera extraña.

Nota : Usted necesita asegurarse de que ninguna de las secuencias de color en el $PS1antes de este código es exeucted están debidamente encerrados en \[y \], y que no hay anidación de ellos.

Tom Hale
fuente
Si bien me gusta este enfoque en teoría, en la práctica no funciona de inmediato (ubuntu 18.04, GNU bash 4.4.19): agregar el código directamente a .bashrc primero da el error bash: local: can only be used in a function, que es trivial de arreglar, y después de eso, no muestra nada porque COLUMNSno está definido: tiene que ser sustituido por $(tput cols). mismo resultado si el fragmento se guarda en un archivo diferente y luego se obtiene .bashrc.
Polentino
1
Gracias @Polentino. He actualizado el código para que se ejecute tput colssi no $COLUMNSestá configurado. Y sí, este código debe estar dentro de una función. Yo uso PROMPT_COMMAND='_prompt_bash_set'y nombro la función _prompt_bash_set.
Tom Hale
2

Solo pensé en tirar el mío aquí. Es casi exactamente lo mismo que el indicador GRML zsh (excepto que las actualizaciones de zsh es un poco mejor en nuevas líneas y espacios posteriores, lo cual es imposible de replicar en bash ... bueno, muy difícil en este momento, al menos).

Pasé unos buenos tres días en esto (solo probé en una computadora portátil con arco), así que aquí hay una captura de pantalla y luego las cosas que van en mi ~ / .bashrc :)

captura de pantalla de bash prompt en acción

advertencia : es un poco loco

importante aparte : cada ^[(como ^[[34m) es realmente el personaje de escape (char)27. La única forma en que sé cómo insertar esto es ingresar ctrl+ ( [v) (es decir, presionar ambos [y vmientras ctrlse mantiene presionado.

# grml battery?
GRML_DISPLAY_BATTERY=1

# battery dir
if [ -d /sys/class/power_supply/BAT0 ]; then
    _PS1_bat_dir='BAT0';
else
    _PS1_bat_dir='BAT1';
fi

# ps1 return and battery
_PS1_ret(){
    # should be at beg of line (otherwise more complex stuff needed)
    RET=$?;

    # battery
    if [[ "$GRML_DISPLAY_BATTERY" == "1" ]]; then
        if [ -d /sys/class/power_supply/$_PS1_bat_dir ]; then
            # linux
            STATUS="$( cat /sys/class/power_supply/$_PS1_bat_dir/status )";
            if [ "$STATUS" = "Discharging" ]; then
                bat=$( printf ' v%d%%' "$( cat /sys/class/power_supply/$_PS1_bat_dir/capacity )" );
            elif [ "$STATUS" = "Charging" ]; then
                bat=$( printf ' ^%d%%' "$( cat /sys/class/power_supply/$_PS1_bat_dir/capacity )" );
            elif [ "$STATUS" = "Full" ] || [ "$STATUS" = "Unknown" ] && [ "$(cat /sys/class/power_supply/$_PS1_bat_dir/capacity)" -gt "98" ]; then
                bat=$( printf ' =%d%%' "$( cat /sys/class/power_supply/$_PS1_bat_dir/capacity )" );
            else
                bat=$( printf ' ?%d%%' "$( cat /sys/class/power_supply/$_PS1_bat_dir/capacity )" );
            fi;
        fi
    fi

    if [[ "$RET" -ne "0" ]]; then
        printf '\001%*s%s\r%s\002%s ' "$(tput cols)" ":( $bat " "^[[0;31;1m" "$RET"
    else
        printf '\001%*s%s\r\002' "$(tput cols)" "$bat "
    fi;
}

_HAS_GIT=$( type 'git' &> /dev/null );

# ps1 git branch
_PS1_git(){
    if ! $_HAS_GIT; then
        return 1;
    fi;
    if [ ! "$( git rev-parse --is-inside-git-dir 2> /dev/null )" ]; then
        return 2;
    fi
    branch="$( git symbolic-ref --short -q HEAD 2> /dev/null )"

    if [ "$branch" ]; then
        printf ' \001%s\002(\001%s\002git\001%s\002)\001%s\002-\001%s\002[\001%s\002%s\001%s\002]\001%s\002' "^[[0;35m" "^[[39m" "^[[35m" "^[[39m" "^[[35m" "^[[32m" "${branch}" "^[[35m" "^[[39m"
    fi;
}

# grml PS1 string
PS1="\n\[\e[F\e[0m\]\$(_PS1_ret)\[\e[34;1m\]${debian_chroot:+($debian_chroot)}\u\[\e[0m\]@\h \[\e[01m\]\w\$(_PS1_git) \[\e[0m\]% "

Todavía estoy trabajando para hacer que los colores sean configurables, pero estoy contento con los colores como están ahora.


Actualmente trabajando en una solución para el ^[personaje loco y el cambio de color fácil :)

dylnmc
fuente
No es Ctrl + [yv simultáneamente, es Ctrl + v seguido de Ctrl + [.
NieDzejkob
0

Puede usar printfpara hacer la alineación correcta:

$ printf "%10s\n" "hello"
     hello

$ PS1='$(printf "%10s" "$somevar")\w\$ '
Pausado hasta nuevo aviso.
fuente
0

Agregando la respuesta de Giles, escribí algo para manejar mejor los colores (siempre que estén encerrados correctamente \[y \]. Es caso por caso y no maneja todos los casos, pero me permite configurar mi PS1L en la misma sintaxis que PS1 y usa la fecha (sin color) como PS1R.

function title {
    case "$TERM" in
    xterm*|rxvt*)
        echo -en "\033]2;$1\007"
        ;;
    *)
        ;;
    esac
}

print_pre_prompt() {
    PS1R=$(date)
    PS1L_exp="${PS1L//\\u/$USER}"
    PS1L_exp="${PS1L_exp//\\h/$HOSTNAME}"
    SHORT_PWD=${PWD/$HOME/~}
    PS1L_exp="${PS1L_exp//\\w/$SHORT_PWD}"
    PS1L_clean="$(sed -r 's:\\\[([^\\]|\\[^]])*\\\]::g' <<<$PS1L_exp)"
    PS1L_exp=${PS1L_exp//\\\[/}
    PS1L_exp=${PS1L_exp//\\\]/}
    PS1L_exp=$(eval echo '"'$PS1L_exp'"')
    PS1L_clean=$(eval echo -e $PS1L_clean)
    title $PS1L_clean
    printf "%b%$(($COLUMNS-${#PS1L_clean}))b\n" "$PS1L_exp" "$PS1R"
}

Aquí está en github: dbarnett / dotfiles / right_prompt.sh . Lo uso en mi .bashrc así:

source $HOME/dotfiles/right_prompt.sh
PS1L='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]'
PS1='\[\033[01;34m\]\w\[\033[00m\]\$ '
PROMPT_COMMAND=print_pre_prompt

Nota: También agregué una nueva línea después de PS1R, lo que no hace ninguna diferencia visual, pero parece evitar que el mensaje se vuelva confuso si se desplaza hacia atrás a través de ciertos comandos en su historial de comandos.

Estoy seguro de que alguien más puede mejorar esto y tal vez generalizar algunos de los casos especiales.

Mu Mind
fuente
0

Aquí hay una solución basada en PROMPT_COMMANDy tput:

function __prompt_command() {
  local EXIT="$?"             # This needs to be first
  history -a
  local COL=$(expr `tput cols` - 8)
    PS1="💻 \[$(tput setaf 196)\][\[$(tput setaf 21)\]\W\[$(tput setaf 196)\]]\[$(tput setaf 190)\]"
    local DATE=$(date "+%H:%M:%S")
  if [ $EXIT != 0 ]; then
    PS1+="\[$(tput setaf 196)\]\$"      # Add red if exit code non 0
    tput sc;tput cuu1; tput cuf $COL;echo "$(tput setaf 196)$DATE"; tput rc
  else
  PS1+="\[$(tput setaf 118)\]\$"
    tput sc;tput cuu1; tput cuf $COL;echo "$(tput setaf 118)$DATE"; tput rc
  fi
  PS1+="\[$(tput setaf 255)\] "
}
PROMPT_COMMAND="__prompt_command"

La magia es realizada por:

tput sc;tput cuu1; tput cuf $COL;echo "$(tput setaf 196)$DATE"; tput rc

Que se desglosa por:

tput sc                       # saved the cursor position
tput cuu1                     # up one line
tput cuf $COL                 # move $COL characters left
echo "$(tput setaf 196)$DATE" # set the colour and print the date
tput rc                       # restore the cursor position

En PS1, tputse escapa con \ [\] para que no se cuente en la longitud mostrada.

Daniel Da Cunha
fuente