Выровнять по праву часть подсказки


27

Я уверен, что я видел кого-то, у кого часть его приглашения выровнена по правому краю в окне терминала, а затем фактический курсор начинался со второй строки. Я знаю, что могу получить вторую строку с "\ n" в PS1, но я не могу понять, как выровнять ее часть вправо. Было ли то, что я увидел, просто добавить пробел между двумя строками?

Ответы:


17

То, что вы хотите, довольно легко сделать, отобразив первую строку перед отображением подсказки. Например, ниже показано приглашение \wслева от первой строки и приглашение \u@\hсправа от первой строки. Он использует $COLUMNSпеременную, которая содержит ширину терминала и $PROMPT_COMMANDпараметр, который оценивается до того, как bash отобразит подсказку.

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
Обратите внимание, что все становится значительно сложнее, если вы хотите получить цветную подсказку слева, поскольку непечатные символы означают, что длина строки не совпадает с количеством отображаемых символов.
Mu Mind

1
И этот, и ответ с наибольшим количеством голосов не работают правильно, если .inputrcимеет set show-mode-in-prompt on. Оба не рассчитывают длину непечатаемых кодов ANSI CSI , и не включают их должным образом \[и, \]как упоминает @Mu Mind. Смотрите этот ответ для решения.
Том Хейл,

26

Основываясь на информации, которую я нашел здесь, я смог найти более простое решение для выравнивания по правому краю с учетом содержимого переменной длины справа или слева, включая поддержку цвета. Добавлено здесь для вашего удобства ...

Примечание по цветам: использование \033выхода в пользу альтернатив, без \[\]группировок, оказывается наиболее совместимым и поэтому рекомендуемым.

Хитрость заключается в том, чтобы сначала написать правую часть, а затем с помощью возврата каретки ( \r) вернуться к началу строки и продолжить перезаписывать содержимое левой части поверх этого следующим образом:

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

Я использую tput colsв Mac OS X для получения ширины терминала / консоли, terminfoпоскольку моя $COLUMNSпеременная не заполнена, envно вы можете заменить заменяемое *значение " " %*s, указав " ${COLUMNS}", или любое другое значение, которое вы предпочитаете.

В следующем примере $RANDOMпоказано, как генерировать содержимое различной длины, включая цвета, и показано, как можно извлечь функции для рефакторинга реализации в многократно используемые функции.

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

Поскольку printfпредполагается, что длина строки равна количеству символов, которое необходимо компенсировать количеством символов, требуемым для отображения цветов, вы всегда найдете его ниже конца экрана из-за непечатных символов ANSI без компенсации. Символы, необходимые для цвета, остаются постоянными, и вы обнаружите, что также printf учитывает изменение длины, как, например, возвращается $RANDOM', что сохраняет правильное выравнивание в такте.

Это не тот случай со специальной Баш строки управляющих последовательностями (т.е.. \u, \w, \h, \t) , Хотя, как это будет только записывать длину 2 , потому что баш будет переводить только их , когда появится подсказка, после Printf вынес строку. Это не влияет на левую сторону, но лучше избегать их справа.

Не имеет значения, если сгенерированный контент будет оставаться постоянной длины, хотя. Как и с параметром времени, \tкоторый всегда будет отображать одинаковое количество символов (8) в течение 24 раз. Нам нужно только учесть компенсацию, необходимую для учета разницы между подсчитанными 2 символами, которая в этих случаях приводит к 8 символам при печати.

Имейте в виду, что вам может потребоваться тройной экранирование \\\некоторых escape-последовательностей, которые в противном случае содержат значение для строк. Как и в следующем примере, текущий экранирование рабочего каталога \wне имеет никакого значения в противном случае, поэтому он работает, как и ожидалось, но время \t, которое означает символ табуляции, не работает, как ожидалось, без тройного экранирования сначала.

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

Использование printfс $COLUMNSработало очень хорошо, что-то вроде:

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

Это правильно оправдало меня.


6

Следующее поместит текущую дату и время в RED на RHS терминала.

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

Преимущества:

  • Работает правильно с цветами и кодами ANSI CSI в приглашении RHS
  • Нет подпроцессов. shellcheckчистый.
  • Работает правильно, если .inputrcимеет set show-mode-in-prompt on.
  • Правильно инкапсулирует несрочную длину, давая символы \[и \]так , что редактирование текста , введенный в командной строке не вызывает приглашение на перепечатку странно.

Примечание : Вы должны гарантировать , что любые цветовые последовательности в $PS1до этого кода exeucted правильно заключены в \[и \]и что нет вложенности из них.


хотя мне нравится этот подход в теории, на практике он не работает "из коробки" (ubuntu 18.04, GNU bash 4.4.19): сначала добавление кода непосредственно в .bashrc выдает ошибку bash: local: can only be used in a function, которую тривиально исправить, и после этого он ничего не показывает, потому что COLUMNSне определен: его нужно заменить на $(tput cols). тот же результат, если фрагмент сохраняется в другом файле, а затем поступает в .bashrc.
Полентино

1
Спасибо @Polentino. Я обновил код для запуска, tput colsесли $COLUMNSне установлен. И да, этот код должен быть внутри функции. Я использую PROMPT_COMMAND='_prompt_bash_set'и называю функцию _prompt_bash_set.
Том Хейл,

2

Я просто думал, что брошу свой сюда. Это почти то же самое, что и GRML-приглашение zsh (за исключением обновлений zsh, оно немного лучше для новых строк и пробелов - что невозможно скопировать в bash ... ну, очень трудно в данный момент, по крайней мере).

Я потратил на это три дня (проверено только на ноутбуке с аркой), так что вот скриншот, а затем материал, который находится в моем ~ / .bashrc :)

снимок экрана с подсказкой bash в действии

предупреждение - это немного сумасшедший

важно в стороне - каждый ^[(такой как ^[[34m) действительно является спасательным персонажем (char)27. Единственный способ, которым я знаю, как вставить это, состоит в том, чтобы ввести ctrl+ ( [v) (то есть нажать одновременно [и, vпока ctrlудерживается нажатой.

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

Я все еще работаю над тем, чтобы сделать цвета настраиваемыми, но я доволен цветами, какими они являются сейчас.


В настоящее время работаем над исправлением сумасшедшего ^[персонажа и легкой сменой цвета :)


Это не Ctrl + [и v одновременно, это Ctrl + v, а затем Ctrl + [.
NieDzejkob


0

Добавляя ответ Джайлза, я написал что-то, чтобы лучше обрабатывать цвета (при условии, что они должным образом заключены в \[и \]. Это индивидуально для каждого случая и не обрабатывает каждый случай, но это позволяет мне установить PS1L в том же синтаксисе, что и PS1). и использует (неокрашенную) дату в качестве 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"
}

Вот это на github: dbarnett / dotfiles / right_prompt.sh . Я использую его в моем .bashrc так:

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

Примечание: я также добавил новую строку после PS1R, которая не имеет визуального различия, но, похоже, не дает подсказке искажаться, если вы прокручиваете назад некоторые команды в своей истории команд.

Я уверен, что кто-то еще может улучшить это, и, возможно, обобщить некоторые из особых случаев.


0

Вот решение на основе PROMPT_COMMANDи 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"

Волшебство выполняется:

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

Который разбит на:

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

В PS1 tputэкранируется с \ [\], поэтому он не учитывается при отображении длины.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.