Aligner à droite une partie de l'invite


27

Je suis sûr que j'ai vu quelqu'un avoir une partie de son invite alignée à droite dans sa fenêtre de terminal, puis que le curseur réel démarre sur une deuxième ligne. Je sais que je peux atteindre la deuxième ligne avec un "\ n" dans la PS1, mais je ne sais pas comment aligner une partie de celle-ci vers la droite. Est-ce que j'ai vu juste un espace ajouté entre les deux chaînes?

Réponses:


17

Ce que vous voulez peut être fait assez facilement en affichant la première ligne avant d'afficher l'invite. Par exemple, ce qui suit affiche une invite de \wà gauche de la première ligne et une invite de \u@\hà droite de la première ligne. Il utilise la $COLUMNSvariable qui contient la largeur du terminal et le $PROMPT_COMMANDparamètre qui est évalué avant que bash affiche l'invite.

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

3
Notez que les choses deviennent beaucoup plus compliquées si vous voulez une invite de gauche colorée, car les caractères non imprimables signifient que la longueur de la chaîne n'est pas la même que le nombre de caractères affichés.
Mu Mind

1
Les deux cela et le plus voté réponse ne fonctionnent pas correctement si .inputrca set show-mode-in-prompt on. Les deux ne calculent pas la longueur des codes ANSI CSI non imprimables et ne les enferment pas correctement dans \[et \]comme mentionné par @Mu Mind. Voir cette réponse pour une résolution.
Tom Hale

26

Sur la base des informations que j'ai trouvées ici, j'ai pu découvrir une solution plus simple pour aligner à droite tout en acceptant un contenu de longueur variable à droite ou à gauche, y compris le support de la couleur. Ajouté ici pour votre commodité ...

Note sur les couleurs: utiliser l' \033évasion en faveur d'alternatives, sans \[\]regroupements, s'avère le plus compatible et donc recommandé.

L'astuce consiste à écrire d'abord le côté droit, puis à utiliser le retour chariot ( \r) pour revenir au début de la ligne et continuer à écraser le contenu du côté gauche en plus, comme suit:

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

J'utilise tput colssur Mac OS X pour récupérer la largeur du terminal / de la console terminfodepuis que ma $COLUMNSvar n'est pas remplie envmais vous pouvez remplacer la " *" valeur remplaçable dans %*s, en fournissant " ${COLUMNS}", ou toute autre valeur que vous préférez, à la place.

L'exemple suivant utilise $RANDOMpour générer un contenu de longueur différente comprend des couleurs et montre comment vous pouvez extraire des fonctions pour refactoriser l'implémentation en fonctions réutilisables.

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

Étant donné printfque la longueur de la chaîne est le nombre de caractères dont nous avons besoin pour compenser la quantité de caractères requise pour le rendu des couleurs, vous la trouverez toujours avant la fin de l'écran en raison des caractères ANSI non imprimés sans compensation. Les caractères requis pour la couleur restent constants et vous constaterez que printf prend également en compte le changement de longueur, tel que renvoyé par $RANDOMexemple ', ce qui maintient notre bon alignement intact.

Ce n'est pas le cas avec des séquences d'échappement rapide bash spéciale (ie. \u, \w, \h, \t) Cependant, car ceux - ci n'enregistrera une longueur de 2 parce que bash ne les traduire lorsque l'invite est affiché, après printf a rendu la chaîne. Cela n'affecte pas le côté gauche, mais il vaut mieux les éviter à droite.

Cela n'a aucune conséquence si le contenu généré reste de longueur constante. Comme avec l' \toption de temps qui rendra toujours le même nombre de caractères (8) pendant 24 fois. Il suffit de prendre en compte la compensation requise pour tenir compte de la différence entre 2 caractères comptés, ce qui donne 8 caractères lors de l'impression, dans ces cas.

Gardez à l'esprit que vous devrez peut-être triple échapper \\\certaines séquences d'échappement qui, autrement, ont un sens pour les chaînes. Comme dans l'exemple suivant, l'échappement du répertoire de travail actuel \wn'a aucune signification sinon il fonctionne comme prévu, mais l'heure \t, ce qui signifie un caractère de tabulation, ne fonctionne pas comme prévu sans l'échapper triple au préalable.

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!


8

L'utilisation printfavec $COLUMNSa très bien fonctionné, quelque chose comme:

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

Cela me le justifiait parfaitement.


6

Ce qui suit mettra la date et l'heure actuelles en ROUGE sur la RHS du 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}"

Avantages:

  • Fonctionne correctement avec les couleurs et les codes CSI ANSI dans l'invite RHS
  • Aucun sous-processus. shellchecknettoyer.
  • Fonctionne correctement si .inputrca set show-mode-in-prompt on.
  • Encapsule correctement les caractères qui ne donnent pas de longueur d'invite dans \[et \]afin que la modification du texte saisi à l'invite n'entraîne pas une réimpression étrange de l'invite.

Note : Vous devez vous assurer que toutes les séquences de couleurs dans le $PS1avant ce code est exeucted sont correctement enfermés dans \[et \]et qu'il n'y a pas d' imbrication d'entre eux.


bien que j'aime cette approche en théorie, en pratique, cela ne fonctionne pas dès le départ (ubuntu 18.04, GNU bash 4.4.19): ajouter le code directement dans .bashrc donne d'abord l'erreur bash: local: can only be used in a function, ce qui est trivial à corriger, et après cela, il ne montre rien car il COLUMNSn'est pas défini: il doit être remplacé par $(tput cols). même résultat si l'extrait est enregistré dans un fichier différent, puis extrait .bashrc.
Polentino

1
Merci @Polentino. J'ai mis à jour le code à exécuter tput colss'il $COLUMNSn'est pas défini. Et oui, ce code devrait être à l'intérieur d'une fonction. J'utilise PROMPT_COMMAND='_prompt_bash_set'et nomme la fonction _prompt_bash_set.
Tom Hale

2

Je pensais juste que je jetterais le mien ici. C'est presque exactement la même chose que l'invite zsh GRML (sauf les mises à jour zsh, elle est un peu meilleure sur les nouvelles lignes et les espaces arrière - ce qui est impossible à répliquer dans bash ... enfin très difficile à ce stade, au moins).

J'ai passé trois bons jours à ce sujet (testé uniquement sur un ordinateur portable exécutant une arche), voici donc une capture d'écran, puis le contenu de mon ~ / .bashrc :)

capture d'écran de l'invite bash en action

avertissement - c'est un peu fou

important côté - chaque ^[(comme ^[[34m) est vraiment le personnage d'échappement (char)27. La seule façon dont je sais comment insérer ceci est d'entrer ctrl+ ( [v) (c'est-à [- dire d'appuyer sur les deux et vpendant que la touche ctrlest maintenue enfoncée.

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

Je travaille toujours à rendre les couleurs configurables, mais je suis satisfait des couleurs telles qu'elles sont maintenant.


Travaille actuellement sur un correctif pour le ^[personnage fou et le changement de couleur facile :)


Ce n'est pas Ctrl + [et v simultanément, c'est Ctrl + v suivi de Ctrl + [.
NieDzejkob


0

En ajoutant la réponse de Giles, j'ai écrit quelque chose pour mieux gérer les couleurs (à condition qu'elles soient correctement enfermées dans \[et \]. C'est au cas par cas et ne gère pas tous les cas, mais cela me permet de définir ma PS1L dans la même syntaxe que la PS1 et utilise la date (non colorée) comme 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"
}

Le voici sur github: dbarnett / dotfiles / right_prompt.sh . Je l'utilise dans mon .bashrc comme ceci:

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

Remarque: J'ai également ajouté une nouvelle ligne après PS1R, ce qui ne fait aucune différence visuelle, mais semble empêcher l'invite de se brouiller si vous faites défiler certaines commandes dans votre historique de commandes.

Je suis sûr que quelqu'un d'autre peut améliorer cela, et peut-être généraliser une partie de la casse spéciale.


0

Voici une solution basée sur PROMPT_COMMANDet 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 magie est réalisée par:

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

Qui se décompose par:

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

Dans PS1, tputest échappé avec \ [\] afin qu'il ne soit pas compté dans la longueur affichée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.