Teil der Eingabeaufforderung nach rechts ausrichten

27

Ich bin sicher, dass jemand einen Teil seiner Eingabeaufforderung in seinem Terminalfenster rechts ausgerichtet hat und der eigentliche Cursor in einer zweiten Zeile beginnt. Ich weiß, dass ich die zweite Zeile mit einem "\ n" in der PS1 erreichen kann, aber ich kann nicht herausfinden, wie ich einen Teil davon nach rechts ausrichte. Wurde zwischen den beiden Zeichenfolgen nur ein Leerzeichen eingefügt?

Felix Andersen
quelle

Antworten:

17

Was Sie wollen, können Sie ziemlich einfach tun, indem Sie die erste Zeile anzeigen, bevor Sie die Eingabeaufforderung anzeigen. Im Folgenden wird beispielsweise eine Eingabeaufforderung \wlinks in der ersten Zeile und eine Eingabeaufforderung \u@\hrechts in der ersten Zeile angezeigt . Dabei wird die $COLUMNSVariable verwendet, die die Breite des Terminals und den $PROMPT_COMMANDParameter enthält, der ausgewertet wird, bevor in der Bash die Eingabeaufforderung angezeigt wird.

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 - hör auf böse zu sein'
quelle
3
Beachten Sie, dass die Dinge erheblich komplizierter werden, wenn Sie eine farbige linke Eingabeaufforderung wünschen, da nicht druckbare Zeichen bedeuten, dass die Länge der Zeichenfolge nicht der Anzahl der angezeigten Zeichen entspricht.
Mu Mind
1
Sowohl diese als auch die am höchsten bewertete Antwort funktionieren nicht richtig, wenn dies der Fall .inputrcist set show-mode-in-prompt on. Beide berechnen nicht die Länge der nicht druckbaren ANSI-CSI-Codes und schließen sie nicht ordnungsgemäß in \[und \]wie von @Mu Mind erwähnt ein. In dieser Antwort finden Sie eine Lösung.
Tom Hale
26

Basierend auf den Informationen, die ich hier gefunden habe, konnte ich eine einfachere Lösung für die Rechtsausrichtung finden, während Inhalte mit variabler Länge rechts oder links einschließlich Unterstützung für Farbe berücksichtigt wurden. Zur Vereinfachung hier hinzugefügt ...

Hinweis zu Farben: Die Verwendung des \033Fluchtweges zugunsten von Alternativen ohne \[\]Gruppierungen erweist sich als am besten kompatibel und wird daher empfohlen.

Der Trick besteht darin, zuerst die rechte Seite zu schreiben, dann mit dem Wagenrücklauf ( \r) zum Zeilenanfang zurückzukehren und den Inhalt der linken Seite darüber hinaus wie folgt zu überschreiben:

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

Ich verwende tput colsMac OS X zum Abrufen der Terminal- / Konsolenbreite, terminfoda meine Variable $COLUMNSnicht ausgefüllt ist. envSie können jedoch den ersetzbaren *Wert in %*s, durch die Angabe von " ${COLUMNS}" oder einen anderen von Ihnen bevorzugten Wert ersetzen .

Das nächste Beispiel, das $RANDOMzum Generieren von Inhalten unterschiedlicher Länge verwendet wird, enthält Farben und zeigt, wie Sie Funktionen extrahieren können, um die Implementierung in wiederverwendbare Funktionen umzugestalten.

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

Da printfdavon ausgegangen wird, dass die Länge der Zeichenfolge der Anzahl der Zeichen entspricht, die zum Rendern der Farben erforderlich sind, befindet sich diese Zeichenfolge aufgrund der nicht gedruckten ANSI-Zeichen ohne Kompensation immer kurz vor dem Ende des Bildschirms. Die für die Farbe erforderlichen Zeichen bleiben konstant, und Sie werden feststellen, dass printf auch die Längenänderung berücksichtigt, die $RANDOMbeispielsweise durch 'zurückgegeben wird, wodurch die richtige Ausrichtung erhalten bleibt.

Dies ist nicht der Fall mit speziellen Bash - Prompt - Escape - Sequenzen (dh. \u, \w, \h, \t) , Obwohl, da diese nur eine Länge von 2 aufzeichnen , weil bash sie nur übersetzen, wenn die Eingabeaufforderung angezeigt wird, nachdem printf die Zeichenfolge verdient gemacht hat. Dies betrifft nicht die linke Seite, aber vermeiden Sie sie am besten auf der rechten Seite.

Es ist jedoch unerheblich, ob der generierte Inhalt konstant bleibt. Wie bei der \tZeitoption, bei der immer die gleiche Anzahl von Zeichen (8) für 24 Mal ausgegeben wird. Wir müssen nur die erforderliche Kompensation berücksichtigen, um die Differenz zwischen 2 gezählten Zeichen auszugleichen, was in diesen Fällen zu 8 Zeichen beim Drucken führt.

Denken Sie daran, dass Sie möglicherweise \\\einige Escape-Sequenzen verdreifachen müssen, die sonst für Zeichenfolgen eine Bedeutung haben. Wie im folgenden Beispiel hat das aktuelle Arbeitsverzeichnis-Escape \wansonsten keine Bedeutung. Es funktioniert also wie erwartet, aber die Zeit \t, dh ein Tabulatorzeichen, funktioniert nicht wie erwartet, ohne dass es zuvor dreimal maskiert wird.

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!

nickl-
quelle
8

Der Umgang printfmit $COLUMNShat sehr gut funktioniert, so etwas wie:

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

Es hat es für mich vollkommen gerechtfertigt.

jmervine
quelle
6

Im Folgenden werden das aktuelle Datum und die aktuelle Uhrzeit auf der rechten Seite des Terminals in ROT angezeigt.

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

Vorteile:

  • Funktioniert ordnungsgemäß mit Farben und ANSI-CSI-Codes in der RHS-Eingabeaufforderung
  • Keine Unterprozesse. shellcheckreinigen.
  • Funktioniert einwandfrei, wenn .inputrchat set show-mode-in-prompt on.
  • Korrekt kapselt die nicht-Prompt Länge gebende Zeichen \[und \]so , dass der Text an der Eingabeaufforderung verursacht nicht die Aufforderung seltsam nachzudrucken eingegeben bearbeiten.

Hinweis : Sie werden sicherstellen müssen, dass alle Farbsequenzen in dem $PS1vor diesem Code exeucted sind richtig eingeschlossen in \[und \]und dass es keine Verschachtelung von ihnen.

Tom Hale
quelle
Während ich diesen Ansatz theoretisch mag, funktioniert er in der Praxis nicht ohne weiteres (Ubuntu 18.04, GNU Bash 4.4.19): Wenn Sie den Code direkt in .bashrc anhängen, erhalten Sie zuerst den Fehler bash: local: can only be used in a function, der trivial zu beheben ist, und danach zeigt es nichts an, weil COLUMNSes nicht definiert ist: es muss durch ersetzt werden $(tput cols). Dasselbe Ergebnis, wenn das Snippet in einer anderen Datei gespeichert und dann in "" bezogen wird .bashrc.
Polentino
1
Vielen Dank @Polentino. Ich habe den Code so aktualisiert, dass er ausgeführt wird, tput colswenn er nicht gesetzt $COLUMNSist. Und ja, dieser Code sollte in einer Funktion enthalten sein. Ich benutze PROMPT_COMMAND='_prompt_bash_set'und benenne die Funktion _prompt_bash_set.
Tom Hale
2

Ich dachte nur, ich würde meine hier reinwerfen. Es ist fast genau dasselbe wie die GRML-zsh-Eingabeaufforderung (mit der Ausnahme, dass zsh-Aktualisierungen bei neuen Zeilen und Leerzeichen etwas besser sind - was in bash unmöglich zu replizieren ist ... na ja, zumindest zu diesem Zeitpunkt sehr schwierig).

Ich habe gute drei Tage damit verbracht (nur auf einem Laptop getestet, auf dem arch läuft), also hier ein Screenshot und dann das, was in meinem ~ / .bashrc steckt :)

Screenshot der Bash-Eingabeaufforderung in Aktion

Warnung - es ist ein bisschen verrückt

nebenbei wichtig - jeder ^[(wie ^[[34m) ist wirklich der Fluchtcharakter (char)27. Die einzige Möglichkeit, dies einzufügen, ist die Eingabe von ctrl+ ( [v) (dh drücken Sie beide Tasten [und halten Sie vdabei ctrlgedrückt).

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

Ich arbeite immer noch daran, die Farben konfigurierbar zu machen, aber ich bin zufrieden mit den Farben, wie sie jetzt sind.


Derzeit wird an einem Fix für den verrückten ^[Charakter und dem einfachen Farbwechsel gearbeitet :)

dylnmc
quelle
Es ist nicht gleichzeitig Strg + [und v, sondern Strg + v gefolgt von Strg + [.
NieDzejkob
0

Mit können Sie printfdie richtige Ausrichtung vornehmen:

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

$ PS1='$(printf "%10s" "$somevar")\w\$ '
Bis auf weiteres angehalten.
quelle
0

Als Ergänzung zu Giles 'Antwort habe ich etwas geschrieben, um Farben besser handhaben zu können (vorausgesetzt, sie sind ordnungsgemäß in \[und eingeschlossen \]. Es ist von Fall zu Fall und behandelt nicht jeden Fall, aber ich kann meine PS1L in der gleichen Syntax wie PS1 einstellen und verwendet das (ungefärbte) Datum als 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"
}

Hier ist es auf Github: dbarnett / dotfiles / right_prompt.sh . Ich benutze es in meinem .bashrc wie folgt:

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

Hinweis: Ich habe nach PS1R auch eine neue Zeile eingefügt, die keinen visuellen Unterschied macht, aber die Eingabeaufforderung nicht verstümmelt, wenn Sie durch bestimmte Befehle in Ihrem Befehlsverlauf zurückblättern.

Ich bin sicher, dass jemand anderes dies verbessern und vielleicht einige der Sonderfälle verallgemeinern kann.

Mu Mind
quelle
0

Hier ist eine Lösung basierend auf PROMPT_COMMANDund 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"

Die Magie wird ausgeführt von:

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

Welches ist unterteilt in:

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

In PS1 tputwird mit \ [\] maskiert, so dass es nicht in der angezeigten Länge gezählt wird.

Daniel Da Cunha
quelle