Alinhar à direita parte do prompt

22

Tenho certeza de que vi alguém ter uma parte do prompt alinhado à direita na janela do terminal e, em seguida, iniciar o cursor real em uma segunda linha. Eu sei que posso conseguir a segunda linha com um "\ n" no PS1, mas não consigo descobrir como alinhar parte dele à direita. Foi o que vi apenas o espaço em branco adicionado entre as duas cordas?

    
por Felix Andersen 11.09.2010 / 19:09

8 respostas

16

O que você deseja pode facilmente ser feito exibindo a primeira linha antes de exibir o prompt. Por exemplo, o seguinte exibe um prompt de \w à esquerda da primeira linha e um prompt de \u@\h à direita da primeira linha. Ele faz uso da variável $COLUMNS que contém a largura do terminal e o parâmetro $PROMPT_COMMAND que é avaliado antes de bash exibir o prompt.

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
    
por 11.09.2010 / 20:34
25

Com base nas informações que encontrei aqui, descobri uma solução mais simples para alinhar à direita, acomodando conteúdo de tamanho variável à direita ou à esquerda, incluindo suporte para cores. Adicionado aqui para sua conveniência ...

Observação sobre cores: usando o 3 escape em favor de alternativas, sem \[\] agrupamentos, prova-se mais compatível e, portanto, recomendado.

O truque é escrever primeiro o lado direito, depois usar o retorno de carro ( \r ) para retornar ao início da linha e continuar a sobrescrever o conteúdo do lado esquerdo, como segue:

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

Estou usando tput cols no Mac OS X para recuperar a largura do terminal / console de terminfo , pois meu $COLUMNS var não está preenchido em env , mas você pode substituir o valor substituível " * " em %*s , fornecendo " ${COLUMNS} " ou qualquer outro valor de sua preferência.

O próximo exemplo usa $RANDOM para gerar conteúdo de comprimento diferente incluindo cores e mostra como você pode extrair funções para refatorar a implementação para funções reutilizáveis.

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

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

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

Como printf assume que o comprimento da string é o número de caracteres que precisamos para compensar a quantidade de caracteres necessária para renderizar as cores, você descobrirá que sempre falta no final da tela devido ao ANSI não impresso. personagens sem compensação. Os caracteres necessários para a cor permanecem constantes e você descobrirá que também o printf leva em conta a alteração no comprimento, conforme retornado por $RANDOM por exemplo ', que mantém nosso alinhamento correto intacto.

Este não é o caso de seqüências especiais de escape de prompt bash (por exemplo, \u , \w , \h , \t ), já que elas gravarão apenas um comprimento de 2 porque bash somente as traduzirá quando o prompt é exibido, depois que o printf renderiza a string. Isso não afeta o lado esquerdo, mas é melhor evitá-los à direita.

De nenhuma consequência se o conteúdo gerado permanecerá em tamanho constante. Como com o tempo \t opção que sempre renderizará a mesma quantidade de caracteres (8) por 24 horas. Nós só precisamos levar em conta a compensação necessária para acomodar a diferença entre 2 caracteres contados, o que resulta em 8 caracteres quando impressos, nestes casos.

Lembre-se de que você pode precisar triplicar o escape \\ de algumas seqüências de escape que, de outra forma, possuem significado para strings. Como no exemplo a seguir, o diretório de trabalho atual escape \w não tem nenhum significado, então funciona como esperado, mas o tempo \t , que significa um caractere de tabulação, não funciona como esperado sem triplo escapar primeiro.

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

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

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

nJoy!

    
por 10.12.2012 / 00:00
7

Usar printf com $COLUMNS funcionou muito bem, algo como:

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

Isso justifica perfeitamente para mim.

    
por 11.11.2011 / 19:13
5

O seguinte colocará a data e hora atuais em RED no RHS do 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}"

Vantagens:

  • Funciona corretamente com cores e códigos ANSI CSI no prompt do RHS
  • Nenhum subprocesso. shellcheck limpo.
  • Funciona corretamente se .inputrc tiver set show-mode-in-prompt on .
  • Encapsula corretamente os caracteres que não dão a duração imediata em \[ e \] , para que a edição do texto digitado no prompt não faça com que o prompt seja reimpresso de maneira estranha.

Observação : você precisará garantir que as sequências de cores no $PS1 antes de este código ser emitido estão contidas corretamente em \[ e \] e que não há aninhamento de eles.

    
por 26.04.2017 / 08:52
2

Eu apenas pensei em jogar a minha aqui. É quase exatamente o mesmo que o prompt do GRML zsh (exceto as atualizações zsh, ele é um pouco melhor em novas linhas e espaços de retorno - o que é impossível replicar no bash ... bem, muito difícil neste momento, pelo menos). / p>

Eu passei uns bons três dias com isso (só testei em um laptop rodando em arco), então aqui está uma captura de tela e depois o material que está no meu ~ / .bashrc:)

aviso-éumpoucolouco

importanteàparte-cada^[(como^[[34m)érealmenteocaracteredeescape(char)27.Aúnicamaneiraqueeuseicomoinseriristoéentrarctrl+([v)(istoé,aperteambos[evenquantoctrlémantidopressionado.

#grmlbattery?GRML_DISPLAY_BATTERY=1#batterydirif[-d/sys/class/power_supply/BAT0];then_PS1_bat_dir='BAT0';else_PS1_bat_dir='BAT1';fi#ps1returnandbattery_PS1_ret(){#shouldbeatbegofline(otherwisemorecomplexstuffneeded)RET=$?;#batteryif[["$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 '
# 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 '%pre%1%*s%s\r%s%pre%2%s ' "$(tput cols)" ":( $bat " "^[[0;31;1m" "$RET"
    else
        printf '%pre%1%*s%s\r%pre%2' "$(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 ' %pre%1%s%pre%2(%pre%1%s%pre%2git%pre%1%s%pre%2)%pre%1%s%pre%2-%pre%1%s%pre%2[%pre%1%s%pre%2%s%pre%1%s%pre%2]%pre%1%s%pre%2' "^[[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\]% "
1%*s%s\r%s%pre%2%s ' "$(tput cols)" ":( $bat " "^[[0;31;1m" "$RET" else printf '%pre%1%*s%s\r%pre%2' "$(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 ' %pre%1%s%pre%2(%pre%1%s%pre%2git%pre%1%s%pre%2)%pre%1%s%pre%2-%pre%1%s%pre%2[%pre%1%s%pre%2%s%pre%1%s%pre%2]%pre%1%s%pre%2' "^[[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\]% "

Ainda estou trabalhando para tornar as cores configuráveis, mas estou feliz com as cores como elas são agora.

Atualmente trabalhando em uma correção para o caractere ^[ e a troca fácil de cores:)

    
por 08.06.2016 / 07:45
0

Você pode usar printf para fazer o alinhamento correto:

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

$ PS1='$(printf "%10s" "$somevar")\w\$ '
    
por 11.09.2010 / 19:21
0

Adicionando a resposta de Giles, eu escrevi algo para lidar melhor com as cores (desde que elas estejam devidamente colocadas em \[ e \] . É caso a caso e não lida com todos os casos, mas isso me permite defina meu PS1L na mesma sintaxe do PS1 e use a data (sem cor) como PS1R.

function title {
    case "$TERM" in
    xterm*|rxvt*)
        echo -en "3]2;$1
source $HOME/dotfiles/right_prompt.sh
PS1L='${debian_chroot:+($debian_chroot)}\[3[01;32m\]\u@\h\[3[00m\]'
PS1='\[3[01;34m\]\w\[3[00m\]\$ '
PROMPT_COMMAND=print_pre_prompt
7" ;; *) ;; 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" }

Aqui está no github: dbarnett / dotfiles / right_prompt.sh . Eu uso no meu .bashrc assim:

function title {
    case "$TERM" in
    xterm*|rxvt*)
        echo -en "3]2;$1
source $HOME/dotfiles/right_prompt.sh
PS1L='${debian_chroot:+($debian_chroot)}\[3[01;32m\]\u@\h\[3[00m\]'
PS1='\[3[01;34m\]\w\[3[00m\]\$ '
PROMPT_COMMAND=print_pre_prompt
7" ;; *) ;; 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" }

Nota: Eu também adicionei uma nova linha após o PS1R, que não faz diferença visual, mas parece impedir que o prompt fique truncado se você rolar através de certos comandos no seu histórico de comandos.

Tenho certeza de que outra pessoa pode melhorar isso e talvez generalizar alguns dos casos especiais.

    
por 04.09.2012 / 17:47
0

Aqui está uma solução baseada em PROMPT_COMMAND e tput :

function __prompt_command() {
  local EXIT="$?"             # This needs to be first
  history -a
  local COL=$(expr 'tput cols' - 8)
    PS1="                                    
por 07.08.2014 / 08:45