Wyrównaj do prawej część monitu

27

Jestem pewien, że widziałem, jak ktoś ma część swojego monitu wyrównaną do prawej w oknie terminala, a następnie kursor zaczyna się od drugiej linii. Wiem, że mogę uzyskać drugą linię za pomocą „\ n” w PS1, ale nie mogę wymyślić, jak wyrównać jej część po prawej stronie. Czy to, co widziałem, zostało dodane po prostu białe spacje między dwoma łańcuchami?

Felix Andersen
źródło

Odpowiedzi:

17

To, czego chcesz, można dość łatwo zrobić, wyświetlając pierwszy wiersz przed wyświetleniem monitu. Na przykład poniżej wyświetla monit \wpo lewej stronie pierwszego wiersza i monit \u@\hpo prawej stronie pierwszego wiersza. Wykorzystuje $COLUMNSzmienną, która zawiera szerokość terminala i $PROMPT_COMMANDparametr, który jest oceniany, zanim bash wyświetli monit.

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- przestań być zły”
źródło
3
Zauważ, że sprawy stają się znacznie bardziej skomplikowane, jeśli chcesz kolorowego lewego znaku zachęty, ponieważ znaki niedrukowalne oznaczają, że długość łańcucha nie jest taka sama jak liczba wyświetlanych znaków.
Mu Mind
1
Zarówno ta, jak i najwyższa głosowana odpowiedź nie działają poprawnie, jeśli tak .inputrcjest set show-mode-in-prompt on. Obie nie obliczyć długość non-prinable kodów ANSI CSI i nie prawidłowo ująć je \[i \]jak wspomniano przez @Mu Umysłu. Zobacz tę odpowiedź, aby uzyskać rozwiązanie.
Tom Hale,
26

Na podstawie informacji, które tu znalazłem, byłem w stanie odkryć prostsze rozwiązanie do wyrównania do prawej strony, jednocześnie uwzględniając zawartość o zmiennej długości po prawej lub lewej stronie, w tym obsługę kolorów. Dodano tutaj dla Twojej wygody ...

Uwaga dotycząca kolorów: użycie \033ucieczki na rzecz alternatyw, bez \[\]grupowania, okazuje się najbardziej kompatybilne i dlatego jest zalecane.

Sztuczka polega na tym, aby najpierw napisać prawą stronę, a następnie użyć znaku powrotu karetki ( \r), aby powrócić na początek wiersza i kontynuować nadpisywanie zawartości lewej strony, następująco:

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

Korzystam tput colsz systemu Mac OS X do pobierania szerokości terminala / konsoli, terminfoponieważ mój $COLUMNSvar nie jest wypełniony, envale możesz zastąpić wymienną *wartość „ ” w%*s , podając „ ${COLUMNS}” lub dowolną inną wartość, którą wolisz.

Następny przykład wykorzystuje $RANDOMdo generowania treści o różnej długości, zawiera kolory i pokazuje, w jaki sposób można wyodrębnić funkcje, aby przefakturować implementację do funkcji wielokrotnego użytku.

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

Ponieważ printfprzyjmujemy, że długość łańcucha jest liczbą znaków, którą musimy zrekompensować ilością znaków wymaganą do renderowania kolorów, zawsze znajdzie się on na końcu ekranu ze względu na niedrukowane znaki ANSI bez kompensacji. Znaki wymagane dla koloru pozostają stałe i przekonasz się, że również printf uwzględnia zmianę długości zwracaną przez$RANDOM na przykład przez ', co pozwala zachować właściwe wyrównanie.

Nie jest to przypadek szczególny bash sekwencji szybka ucieczka (tj. \u, \w, \h,\t ), Choć, jak będą one nagrywać tylko długość 2, ponieważ bash będzie tylko tłumaczyć je, gdy zostanie wyświetlony monit, po printf świadczył ciąg. Nie wpływa to na lewą stronę, ale najlepiej unikać ich po prawej stronie.

Bez żadnego znaczenia, jeśli wygenerowana treść pozostanie na stałej długości. Podobnie jak w przypadku \topcji time , która zawsze renderuje tę samą liczbę znaków (8) przez 24 godziny. Musimy jedynie uwzględnić kompensację wymaganą do uwzględnienia różnicy między zliczonymi 2 znakami, która w tych przypadkach daje 8 znaków po wydrukowaniu.

Należy pamiętać, że może być konieczne potrójne ucieczka \\\niektórych sekwencji ucieczki, które w przeciwnym razie mają znaczenie dla łańcuchów. Podobnie jak w poniższym przykładzie, bieżące escape katalogu roboczego \wnie ma żadnego znaczenia, w przeciwnym razie działa zgodnie z oczekiwaniami, ale czas \t, który oznacza znak tabulacji, nie działa zgodnie z oczekiwaniami bez potrójnego jego ucieczki.

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!

nikiel
źródło
8

Używanie printfz $COLUMNSdziałało naprawdę dobrze, coś takiego:

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

To słusznie usprawiedliwiało to dla mnie idealnie.

jmervine
źródło
6

Poniższe umieści bieżącą datę i godzinę na CZERWONY na RHS terminala.

# 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}"

Zalety:

  • Działa poprawnie z kolorami i kodami ANSI CSI w wierszu polecenia RHS
  • Brak podprocesów. shellcheckczysty.
  • Działa poprawnie, jeśli .inputrcma set show-mode-in-prompt on.
  • Prawidłowo oddaje znaki non-szybka długości dający się \[i \]tak, że edytowanie tekstu wprowadzonego w wierszu nie powoduje zachęty do przedruku dziwnie.

Uwaga : Musisz upewnić się, że wszelkie sekwencje kolorów w $PS1przed tym kodem jest exeucted są prawidłowo zamknięte w \[i \]a, że nie ma ich gniazdowania.

Tom Hale
źródło
chociaż podoba mi się to podejście teoretycznie, w praktyce nie działa ono od razu po wyjęciu z pudełka (ubuntu 18.04, GNU bash 4.4.19): dodanie kodu bezpośrednio do .bashrc najpierw daje błąd bash: local: can only be used in a function, który jest trywialny do naprawienia, i potem nic nie pokazuje, ponieważ COLUMNSnie jest zdefiniowane: należy go zastąpić $(tput cols). taki sam wynik, jeśli fragment zostanie zapisany w innym pliku, a następnie pobrany do .bashrc.
Polentino,
1
Dzięki @Polentino. Zaktualizowałem kod, aby działał, tput colsjeśli $COLUMNSjest wyłączony. I tak, ten kod powinien znajdować się w funkcji. Używam PROMPT_COMMAND='_prompt_bash_set'i nazywam funkcję _prompt_bash_set.
Tom Hale,
2

Pomyślałem, że wrzucę tu moją. Jest prawie dokładnie taki sam, jak podpowiedź GRML zsh (z wyjątkiem aktualizacji zsh, podpowiedź jest nieco lepsza na nowych liniach i spacjach - co jest niemożliwe do odtworzenia w bash ... przynajmniej bardzo trudne w tym momencie).

Spędziłem na tym dobre trzy dni (testowałem tylko na laptopie z uruchomionym łukiem), więc oto zrzut ekranu, a potem rzeczy, które trafiają do mojego ~ / .bashrc :)

zrzut ekranu zachęty bash w akcji

ostrzeżenie - to trochę szalone

ważne na bok - każda ^[(taka jak ^[[34m) jest naprawdę postacią ucieczki (char)27. Jedynym sposobem, wiem jak wstawić ten ma wejść ctrl+ ( [v) (tj hit zarówno [i vgdy ctrljest wciśnięty.

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

Nadal pracuję nad konfiguracją kolorów, ale cieszę się z kolorów, jakie są teraz.


Obecnie pracuję nad poprawką dla szalonej ^[postaci i łatwym przełączaniem kolorów :)

dylnmc
źródło
To nie jest Ctrl + [i v jednocześnie, to Ctrl + v, po którym następuje Ctrl + [.
NieDzejkob,
0

Możesz użyć, printfaby wykonać prawidłowe wyrównanie:

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

$ PS1='$(printf "%10s" "$somevar")\w\$ '
Wstrzymano do odwołania.
źródło
0

Dodając odpowiedź Gilesa, napisałem coś, aby lepiej obsługiwać kolory (pod warunkiem, że są one odpowiednio ujęte w \[i \]. Jest to przypadek po przypadku i nie obsługuje wszystkich przypadków, ale pozwala mi ustawić PS1L w tej samej składni co PS1 i używa (niekolorowej) daty jako 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"
}

Tutaj jest na github: dbarnett / dotfiles / right_prompt.sh . Używam go w moim .bashrc w następujący sposób:

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

Uwaga: Dodałem również nowy wiersz po PS1R, który nie ma żadnej różnicy wizualnej, ale wydaje się, że powstrzymuje monit przed zniekształceniem, jeśli przewiniesz niektóre polecenia w historii poleceń.

Jestem pewien, że ktoś inny może to poprawić i być może uogólni niektóre przypadki wyjątkowe.

Mu Mind
źródło
0

Oto rozwiązanie oparte na PROMPT_COMMANDa 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"

Magię wykonują:

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

Które dzieli się na:

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

W PS1 tputjest poprzedzany znakiem \ [\], aby nie był liczony w wyświetlanej długości.

Daniel Da Cunha
źródło