Alinhar à direita parte do prompt

27

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

Felix Andersen
fonte

Respostas:

17

O que você deseja pode ser feito facilmente exibindo a primeira linha antes de exibir o prompt. Por exemplo, a seguir, é exibido um prompt \wà esquerda da primeira linha e um prompt \u@\hà direita da primeira linha. Utiliza a $COLUMNSvariável que contém a largura do terminal e o $PROMPT_COMMANDparâmetro que é avaliado antes do 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
Gilles 'SO- parar de ser mau'
fonte
3
Observe que as coisas ficam significativamente mais complicadas se você desejar um prompt colorido à esquerda, pois os caracteres que não imprimem significam que o comprimento da string não é igual ao número de caracteres exibidos.
Mu Mind
1
Tanto esta como a resposta mais votada não funcionam corretamente se .inputrctiverem set show-mode-in-prompt on. Ambos não calculam o comprimento dos códigos ANSI CSI não imprimíveis e não os incluem adequadamente no \[e \]como mencionado pelo @Mu Mind. Veja esta resposta para uma resolução.
Tom Hale
26

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

Nota sobre cores: usar a \033fuga em favor de alternativas, sem \[\]agrupamentos, mostra-se mais compatível e, portanto, recomendado.

O truque é escrever primeiro o lado direito, depois usar return ( \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 colsno Mac OS X para recuperar a largura do terminal / console, terminfojá que meu $COLUMNSvar não está preenchido, envmas você pode substituir o *valor " " substituível em %*s, fornecendo " ${COLUMNS}" ou qualquer outro valor de sua preferência.

O próximo exemplo usado $RANDOMpara gerar diferentes tamanhos de conteúdo inclui cores e mostra como você pode extrair funções para refatorar a implementação para funções reutilizáveis.

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

Como printfo comprimento da string é o número de caracteres que precisamos para compensar a quantidade de caracteres necessária para renderizar as cores, você o encontrará sempre aquém do final da tela por causa dos caracteres ANSI não impressos sem compensação. Os caracteres necessários para a cor permanecem constantes e você descobrirá que também printf leva em consideração a alteração no comprimento, conforme retornado por, $RANDOMpor exemplo, 'que mantém nosso alinhamento correto intacto.

Este não é o caso com sequências linha de escape Bash especial (ou seja,. \u, \w, \h, \t), Embora, uma vez que estes só irá gravar um comprimento de 2, porque festa só irá traduzir-los quando a solicitação é apresentada, após printf tornou a corda. Isso não afeta o lado esquerdo, mas é melhor evitá-los à direita.

No entanto, não importa se o conteúdo gerado permanecerá em comprimento constante. Como na \topção de hora, que sempre renderiza a mesma quantidade de caracteres (8) por 24 horas. Somente precisamos levar em consideração a compensação necessária para acomodar a diferença entre 2 caracteres contados, o que resulta em 8 caracteres quando impressos, nesses casos.

Lembre-se de que pode ser necessário triplicar o escape de \\\algumas seqüências de escape que, de outra forma, mantêm significado para as strings. Como no exemplo a seguir, o escape do diretório de trabalho atual \wnão tem significado, caso contrário, ele funciona conforme o esperado, mas o tempo \t, que significa um caractere de tabulação, não funciona como o esperado sem o escape triplo primeiro.

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-
fonte
8

Usar printfcom $COLUMNSfuncionou muito bem, algo como:

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

Isso justificava perfeitamente para mim.

jmervine
fonte
6

A seguir, colocaremos a data e hora atuais em VERMELHO 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
  • Sem subprocessos. shellchecklimpar \ limpo.
  • Funciona corretamente se .inputrctiver set show-mode-in-prompt on.
  • Encapsula corretamente os caracteres que não fornecem comprimento de prompt \[ e, \]para que a edição do texto digitado no prompt não faça com que o prompt seja reimpresso de maneira estranha.

Nota : Você vai precisar para assegurar que quaisquer sequências de cores no $PS1antes deste código é exeucted estão devidamente fechado em \[e \]e que não há assentamento deles.

Tom Hale
fonte
Embora eu goste dessa abordagem na teoria, na prática ela não funciona imediatamente (ubuntu 18.04, GNU bash 4.4.19): acrescentar o código diretamente no .bashrc primeiro fornece o erro bash: local: can only be used in a function, que é trivial de corrigir e depois disso, ele não mostra nada porque COLUMNSnão está definido: deve ser substituído por $(tput cols). mesmo resultado se o snippet for salvo em um arquivo diferente e, em seguida, originado em .bashrc.
Polentino 03/09
1
Obrigado @Polentino. Atualizei o código para executar tput colsse $COLUMNSnão estiver definido. E sim, esse código deve estar dentro de uma função. Eu uso PROMPT_COMMAND='_prompt_bash_set'e nomeio a função _prompt_bash_set.
21418 Tom Hale #
2

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

Passei uns bons três dias nisso (testado apenas em um laptop executando o arco), então aqui está uma captura de tela e depois as coisas que aparecem no meu ~ / .bashrc :)

captura de tela do prompt do bash em ação

Atenção - é um pouco louco

importante a parte - todo ^[(como ^[[34m) é realmente o caractere de escape (char)27. A única maneira de saber como inserir isso é inserir ctrl+ ( [v) (ou seja, pressionar os dois [e venquanto ctrlé pressionado.

# 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\]% "

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


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

dylnmc
fonte
Não é Ctrl + [e v simultaneamente, é Ctrl + v seguido por Ctrl + [.
NieDzejkob
0

Você pode usar printfpara fazer o alinhamento correto:

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

$ PS1='$(printf "%10s" "$somevar")\w\$ '
Pausado até novo aviso.
fonte
0

Acrescentando a resposta de Giles, escrevi algo para lidar melhor com as cores (desde que sejam apropriadamente incluídas \[e \]. É caso a caso e não lida com todos os casos, mas permite definir meu PS1L na mesma sintaxe do PS1 e usa a data (sem cor) 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"
}

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

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: Eu também adicionei uma nova linha após o PS1R, que não faz diferença visual, mas parece impedir que o prompt seja distorcido se você rolar para trás através de certos comandos no seu histórico de comandos.

Tenho certeza que alguém pode melhorar isso, e talvez generalizar alguns dos casos especiais.

Mu Mind
fonte
0

Aqui está uma solução baseada em PROMPT_COMMANDe 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"

A mágica é realizada por:

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

Qual é dividido 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

No PS1, tputé escapado com \ [\] para que não seja contado no comprimento exibido.

Daniel Da Cunha
fonte