22

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

8 ответов8

25

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

Примечание по цветам: использование экранирования \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 ', что сохраняет правильное выравнивание в такте.

Это не относится к специальным escape-последовательностям подсказок bash (т. \u , \w , \h , \t), поскольку они записывают только длину 2, потому что bash будет переводить их только при отображении подсказки после printf отобразил строку. Это не влияет на левую сторону, но лучше избегать их справа.

Не имеет значения, если сгенерированный контент будет оставаться постоянной длины, хотя. Как и в случае с параметром time \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!

16

То, что вы хотите, довольно легко сделать, отобразив первую строку перед отображением подсказки. Например, ниже показано приглашение \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
7

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

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

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

5

Следующее поместит текущую дату и время в 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 перед выполнением этого кода правильно заключены в \[ и \] и что их нет вложения.

2

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

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

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

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

Важно отметить, что каждый ^[ (например, ^[[34m) действительно является escape-символом (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\]% "

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


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

0

Вы можете использовать printf для правильного выравнивания:

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

$ PS1='$(printf "%10s" "$somevar")\w\$ '
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 экранируется с помощью \[\], поэтому он не учитывается при отображении длины.

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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .