Я уверен, что я видел кого-то, у кого часть его приглашения выровнена по правому краю в окне терминала, а затем фактический курсор начинался со второй строки. Я знаю, что могу получить вторую строку с "\n" в PS1, но я не могу понять, как выровнять ее часть вправо. Было ли то, что я увидел, просто добавить пробел между двумя строками?
8 ответов
Основываясь на информации, которую я нашел здесь, я смог найти более простое решение для выравнивания по правому краю с учетом содержимого переменной длины справа или слева, включая поддержку цвета. Добавлено здесь для вашего удобства ...
Примечание по цветам: использование экранирования \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!
То, что вы хотите, довольно легко сделать, отобразив первую строку перед отображением подсказки. Например, ниже показано приглашение \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
Использование printf
с $COLUMNS
работало очень хорошо, что-то вроде:
printf "%${COLUMNS}s\n" "hello"
Это правильно оправдало это для меня.
Следующее поместит текущую дату и время в 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
перед выполнением этого кода правильно заключены в \[
и \]
и что их нет вложения.
Я просто думал, что брошу свой сюда. Это почти то же самое, что и GRML-приглашение zsh (за исключением обновлений zsh, оно немного лучше для новых строк и пробелов - что невозможно воспроизвести в bash ... ну, очень трудно в данный момент, по крайней мере).
Я потратил на это три дня (проверено только на ноутбуке с аркой), так что вот скриншот, а затем материал, который находится в моем ~/.bashrc :)
предупреждение - это немного сумасшедший
Важно отметить, что каждый ^[
(например, ^[[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\]% "
Я все еще работаю над тем, чтобы сделать цвета настраиваемыми, но я доволен цветами, какими они являются сейчас.
В настоящее время работает над исправлением для сумасшедшего ^[
характер и легкое переключение цветов :)
Вы можете использовать printf
для правильного выравнивания:
$ printf "%10s\n" "hello"
hello
$ PS1='$(printf "%10s" "$somevar")\w\$ '
Вот решение, основанное на 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
экранируется с помощью \[\], поэтому он не учитывается при отображении длины.
Добавляя ответ Джайлза, я написал что-то для лучшей обработки цветов (при условии, что они правильно заключены в \[
и \]
. Это в каждом конкретном случае и не обрабатывает каждый случай, но позволяет мне установить 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, которая не имеет визуальной разницы, но, похоже, не дает подсказке искажаться, если вы прокручиваете назад некоторые команды в своей истории команд.
Я уверен, что кто-то еще может улучшить это, и, возможно, обобщить некоторые из особых случаев.