จัดแนวส่วนขวาของพรอมต์


25

ฉันแน่ใจว่าฉันเห็นใครบางคนมีส่วนหนึ่งของการพร้อมท์ของพวกเขาชิดขวาในหน้าต่างเทอร์มินัลของพวกเขาแล้วมีเคอร์เซอร์ที่แท้จริงเริ่มต้นในบรรทัดที่สอง ฉันรู้ว่าฉันสามารถบรรลุบรรทัดที่สองด้วย "\ n" ใน PS1 แต่ฉันไม่สามารถหาวิธีจัดแนวของส่วนทางด้านขวา เป็นสิ่งที่ฉันเห็นเพียงแค่การเพิ่มช่องว่างระหว่างสองสาย?

คำตอบ:


16

สิ่งที่คุณต้องการสามารถทำได้อย่างง่ายดายโดยการแสดงบรรทัดแรกก่อนแสดงข้อความแจ้ง ตัวอย่างเช่นต่อไปนี้แสดงพรอมต์\wทางด้านซ้ายของบรรทัดแรกและพรอมต์\u@\hที่ด้านขวาของบรรทัดแรก มันใช้ประโยชน์จาก$COLUMNSตัวแปรที่มีความกว้างของเทอร์มินัลและ$PROMPT_COMMANDพารามิเตอร์ที่มีการประเมินก่อนที่ทุบตีจะแสดงพรอมต์

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 ดูคำตอบนี้สำหรับการแก้ปัญหา
Tom Hale

26

จากข้อมูลที่ฉันพบที่นี่ฉันสามารถค้นพบวิธีแก้ปัญหาที่ง่ายกว่าในการจัดแนวขวาในขณะที่รองรับเนื้อหาความยาวผันแปรทางด้านขวาหรือซ้ายรวมถึงการรองรับสี เพิ่มที่นี่เพื่อความสะดวกของคุณ ...

หมายเหตุเกี่ยวกับสี: การใช้การ\033หลบหลีกเพื่อเป็นทางเลือกโดยไม่มี\[\]การจัดกลุ่มเป็นการพิสูจน์ความเข้ากันได้มากที่สุดและแนะนำให้ใช้

เคล็ดลับคือการเขียนทางด้านขวามือก่อนจากนั้นใช้ carriage return ( \r) เพื่อกลับไปที่จุดเริ่มต้นของบรรทัดและเขียนทับเนื้อหาด้านซ้ายมือที่ด้านบนของสิ่งต่อไปนี้:

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

ฉันใช้tput colsบน Mac OS X เพื่อดึงความกว้างขั้ว / คอนโซลจากterminfoตั้งแต่ฉัน$COLUMNSvar ไม่ได้บรรจุอยู่ในenvแต่คุณอาจทดแทนทดแทน " *" ค่าใน%*sโดยการให้ " ${COLUMNS}" หรือค่าอื่น ๆ ที่คุณต้องการแทน

ตัวอย่างถัดไปใช้$RANDOMในการสร้างเนื้อหาความยาวที่แตกต่างกันรวมถึงสีและแสดงวิธีที่คุณอาจแยกฟังก์ชั่นเพื่อ refactor หรือการนำไปใช้กับฟังก์ชั่นที่นำมาใช้ซ้ำ

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 อักขระเมื่อพิมพ์ในกรณีเหล่านี้

โปรดทราบว่าคุณอาจจำเป็นต้องหลีกเลี่ยงสาม\\\ลำดับการหลีกเลี่ยงบางอย่างซึ่งมีความหมายกับสตริง เช่นเดียวกับตัวอย่างต่อไปนี้การหลีกเลี่ยงไดเรกทอรีการทำงานปัจจุบัน\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!


7

ใช้printfกับ$COLUMNSทำงานได้ดีจริง ๆ บางอย่างเช่น:

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

มันถูกต้องแล้วมันสมบูรณ์แบบสำหรับฉัน


5

ต่อไปนี้จะใส่วันที่และเวลาปัจจุบันเป็นสีแดงบน 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สะอาด
  • ทำงานได้อย่างถูกต้องถ้ามี.inputrcset show-mode-in-prompt on
  • encapsulate อักขระที่ไม่ได้ให้ความยาวพร้อมรับคำอย่างถูกต้อง\[และ\]เพื่อให้การแก้ไขข้อความที่ป้อนที่พร้อมต์ไม่ทำให้พรอมต์พิมพ์ซ้ำอย่างผิดปกติ

หมายเหตุ : คุณจะต้องตรวจสอบให้แน่ใจว่าลำดับสีใด ๆ ใน$PS1ก่อนที่โค้ดนี้จะถูกวางไว้อย่างถูกต้อง\[และ\]ไม่มีการซ้อนกัน


ในขณะที่ฉันชอบวิธีนี้ในทางทฤษฎีในทางปฏิบัติมันไม่ทำงานนอกกรอบ (ubuntu 18.04, GNU ทุบตี 4.4.19): การผนวกรหัสโดยตรงใน. bashrc ก่อนจะให้ข้อผิดพลาดbash: local: can only be used in a functionซึ่งเป็นเรื่องง่ายที่จะแก้ไขและ หลังจากนั้นก็ไม่ได้แสดงอะไรเพราะไม่ได้ถูกกำหนดมันจะต้องมีการแทนที่ด้วยCOLUMNS ผลเดียวกันถ้าข้อมูลโค้ดจะถูกบันทึกไว้ในไฟล์ที่แตกต่างกันและจากนั้นมาลงใน$(tput cols) .bashrc
Polentino

1
ขอบคุณ @ Polentino ฉันได้อัปเดตรหัสเพื่อให้ทำงานtput colsถ้า$COLUMNSไม่ได้ตั้งค่า และใช่รหัสนี้ควรอยู่ในฟังก์ชั่น ผมใช้และตั้งชื่อฟังก์ชั่นPROMPT_COMMAND='_prompt_bash_set' _prompt_bash_set
Tom Hale

2

ฉันแค่คิดว่าฉันจะโยนของฉันที่นี่ มันเกือบจะเหมือนกับพรอมต์ GRML zsh (ยกเว้นการอัปเดต zsh มันกระตุ้นให้ดีขึ้นเล็กน้อยในบรรทัดใหม่และช่องว่างด้านหลัง - ซึ่งเป็นไปไม่ได้ที่จะทำซ้ำในทุบตี ... ยากมากในเวลานี้อย่างน้อย)

ฉันใช้เวลาสามวันในการทดสอบนี้ (ทดสอบเฉพาะบนแล็ปท็อปที่ทำงานด้วยโค้ง) ดังนั้นนี่คือภาพหน้าจอและจากนั้นเนื้อหาที่อยู่ใน ~ / .bashrc ของฉัน :)

สกรีนช็อตของ bash prompt in action

เตือน - มันบ้าไปหน่อย

ที่สำคัญนอกเหนือ - ทุก^[(เช่น^[[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

เพิ่มคำตอบของไจล์สฉันเขียนบางสิ่งบางอย่างเพื่อจัดการกับสีได้ดีขึ้น (หากปิดไว้อย่างถูกต้อง\[และ\]เป็นกรณี ๆ ไปและไม่สามารถจัดการได้ทุกกรณี และใช้วันที่ (ไม่มีสี) เป็น 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จะถูก Escape ด้วย \ [\] เพื่อไม่ให้นับตามความยาวที่แสดง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.