Auffüllen von Zeichen in printf


106

Ich schreibe ein Bash-Shell-Skript, um anzuzeigen, ob ein Prozess ausgeführt wird oder nicht.

Bisher habe ich folgendes verstanden:

printf "%-50s %s\n" $PROC_NAME [UP]

Der Code gibt mir diese Ausgabe:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Ich möchte die Lücke zwischen den beiden Feldern mit einem '-' oder '*' füllen, um sie besser lesbar zu machen. Wie mache ich das, ohne die Ausrichtung der Felder zu stören?

Die Ausgabe, die ich möchte, ist:

JBoss -------------------------------------------  [DOWN]

GlassFish ---------------------------------------  [UP]

verylongprocessname -----------------------------  [UP]

Antworten:


76

Pure Bash, keine externen Dienstprogramme

Diese Demonstration ist vollständig gerechtfertigt, aber Sie können das Subtrahieren der Länge der zweiten Zeichenfolge einfach weglassen, wenn Sie unregelmäßige Linien wünschen.

pad=$(printf '%0.1s' "-"{1..60})
padlength=40
string2='bbbbbbb'
for string1 in a aa aaaa aaaaaaaa
do
     printf '%s' "$string1"
     printf '%*.*s' 0 $((padlength - ${#string1} - ${#string2} )) "$pad"
     printf '%s\n' "$string2"
     string2=${string2:1}
done

Leider muss bei dieser Technik die Länge der Pad-Zeichenfolge fest codiert werden, um länger als die längste zu sein, die Sie für erforderlich halten. Die Pad-Länge kann jedoch wie gezeigt variabel sein. Sie können jedoch die erste Zeile durch diese drei ersetzen, um eine Variable für die Länge des Pads verwenden zu können:

padlimit=60
pad=$(printf '%*s' "$padlimit")
pad=${pad// /-}

Das Pad ( padlimitund padlength) könnte also auf der Terminalbreite ( $COLUMNS) basieren oder aus der Länge der längsten Datenzeichenfolge berechnet werden.

Ausgabe:

a--------------------------------bbbbbbb
aa--------------------------------bbbbbb
aaaa-------------------------------bbbbb
aaaaaaaa----------------------------bbbb

Ohne die Länge der zweiten Zeichenfolge zu subtrahieren:

a---------------------------------------bbbbbbb
aa--------------------------------------bbbbbb
aaaa------------------------------------bbbbb
aaaaaaaa--------------------------------bbbb

Die erste Zeile könnte stattdessen das Äquivalent sein (ähnlich wie sprintf):

printf -v pad '%0.1s' "-"{1..60}

oder ähnlich für die dynamischere Technik:

printf -v pad '%*s' "$padlimit"

Sie können alles in einer Zeile drucken, wenn Sie möchten:

printf '%s%*.*s%s\n' "$string1" 0 $((padlength - ${#string1} - ${#string2} )) "$pad" "$string2"

1
Könnten Sie den Teil printf '% *. * S' ... etwas erklären?
Édouard Lopez

3
@EdouardLopez: Das erste Sternchen wird in der Argumentliste durch die Null ersetzt. Das zweite Sternchen wird durch das Ergebnis der Berechnung im zweiten Argument ersetzt. Das Ergebnis für die Zeichenfolgen "aaaa" und "bbbbb" ist beispielsweise '%0.31s'. Die Zeichenfolge (das letzte Argument) wird auf die nach dem Punkt angegebene Länge abgeschnitten. Die Null verhindert, dass Leerzeichen ausgegeben werden. Es werden also 31 Bindestriche ausgegeben.
Bis auf weiteres angehalten.

1
Diese Seite könnte helfen, @ Tennis Williamson Antwort zu verstehen: wiki.bash-hackers.org/commands/builtin/printf#modifiers
Édouard Lopez

{1..60} in Bedarf 60 als Variable; ... wie "var = 60"
Reegan Miranda

@ReeganMiranda: Diese Technik funktioniert so, dass Sie den Wert fest auf den größten Wert codieren, den Sie benötigen, und padlengthdie tatsächliche Länge für die Ausgabe auswählen.
Bis auf weiteres angehalten.

68

Pure Bash. Verwenden Sie die Länge des Werts von 'PROC_NAME' als Offset für die feste Zeichenfolge 'line':

line='----------------------------------------'
PROC_NAME='abc'
printf "%s %s [UP]\n" $PROC_NAME "${line:${#PROC_NAME}}"
PROC_NAME='abcdef'
printf "%s %s [UP]\n" $PROC_NAME "${line:${#PROC_NAME}}"

Das gibt

abc ------------------------------------- [UP]
abcdef ---------------------------------- [UP]

Die Magie ist die $ {line: $ {# PROC_NAME}}, die die Bash-Teilstring-Extraktion verwendet, um nur von einem Punkt der variablen Zeile zurückzukehren, der so eingestellt ist, dass er mit der Anzahl der Zeichen in PROC_NAME beginnt. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav

Beachten Sie, dass dies nicht den Fall behandelt, in dem PROC_NAMELeerzeichen vorhanden sind, es sei denn, sie sind bereits maskiert. Sie erhalten eine Zeile mit jeweils zwei Token und dann [UP] für jeweils zwei durch Leerzeichen getrennte Token in Ihrer Variablen und dann eine einzelne Zeile am Ende mit Ihrem lineText abzüglich der Gesamtlänge Ihrer Eingabezeichenfolge. Seien Sie also vorsichtig, da dies in einem komplexen Skript zu interessanten und möglicherweise unsicheren Fehlern führen kann. Ansonsten kurz und einfach. :)
Dodexaeder

19

Triviale (aber funktionierende) Lösung:

echo -e "---------------------------- [UP]\r$PROC_NAME "

4
Aber nur an einem Terminal. Wenn die Ausgabe an eine Datei gesendet wird, ist dies ein Chaos.
Thkala

4
Was erwarten Sie wirklich von einer trivialen Lösung?!? voll funktionsfähig auch mit Ausgabeumleitung?!? ]: P
Nicola Leoni

14

Ich denke, das ist die einfachste Lösung. Pure Shell Builtins, keine Inline-Mathematik. Es leiht sich aus früheren Antworten.

Nur Teilzeichenfolgen und die Metavariable $ {# ...}.

A="[>---------------------<]";

# Strip excess padding from the right
#

B="A very long header"; echo "${A:0:-${#B}} $B"
B="shrt hdr"          ; echo "${A:0:-${#B}} $B"

Produziert

[>----- A very long header
[>--------------- shrt hdr


# Strip excess padding from the left
#

B="A very long header"; echo "${A:${#B}} $B"
B="shrt hdr"          ; echo "${A:${#B}} $B"

Produziert

-----<] A very long header
---------------<] shrt hdr

12

Es gibt keine Möglichkeit, mit etwas anderem als Leerzeichen zu füllen printf. Sie können verwenden sed:

printf "%-50s@%s\n" $PROC_NAME [UP] | sed -e 's/ /-/g' -e 's/@/ /' -e 's/-/ /'

7
+1 Es gibt ein Problem, wenn PROC_NAME einen Bindestrich enthält - leicht zu lösen mit einem zusätzlichen @:printf "%-50s@%s\n" ${PROC_NAME}@ [UP] | sed -e 's/ /-/g' -e 's/-@/ /' -e 's/@-/ /'
thkala

9
echo -n "$PROC_NAME $(printf '\055%.0s' {1..40})" | head -c 40 ; echo -n " [UP]"

Erläuterung:

  • printf '\055%.0s' {1..40}- Erstellen Sie 40 Bindestriche
    (Bindestrich wird als Option interpretiert, verwenden Sie stattdessen maskierten ASCII-Code)
  • "$PROC_NAME ..." - Verketten Sie $ PROC_NAME und Bindestriche
  • | head -c 40 - Schneiden Sie die Zeichenfolge auf die ersten 40 Zeichen

Seltsam, wenn ich es mache printf 'x' {1..40}, druckt es nur einzelne xhmmm
Krystian

@Krystian, weil Sie das Format nicht kopiert haben: `printf 'x% .0s' {1..40}` druckt 40 xs
artm

Um zu vermeiden, dass der Bindestrich als Option interpretiert wird, kann ein doppelter Bindestrich verwendet werden, um zu signalisieren, dass es sich bei dem Rest um printf -- "-%.0s" {1..40}
Nichtoptionsargumente handelt

7

Dieser ist noch einfacher und führt keine externen Befehle aus.

$ PROC_NAME="JBoss"
$ PROC_STATUS="UP"
$ printf "%-.20s [%s]\n" "${PROC_NAME}................................" "$PROC_STATUS"

JBoss............... [UP]

5

Einfach, aber es funktioniert:

printf "%-50s%s\n" "$PROC_NAME~" "~[$STATUS]" | tr ' ~' '- '

Anwendungsbeispiel:

while read PROC_NAME STATUS; do  
    printf "%-50s%s\n" "$PROC_NAME~" "~[$STATUS]" | tr ' ~' '- '
done << EOT 
JBoss DOWN
GlassFish UP
VeryLongProcessName UP
EOT

Ausgabe an stdout:

JBoss -------------------------------------------- [DOWN]
GlassFish ---------------------------------------- [UP]
VeryLongProcessName ------------------------------ [UP]

4

mit echonur

Die Antwort von @Dennis Williamson funktioniert einwandfrei, außer dass ich versucht habe, dies mit Echo zu tun. Mit Echo können Zeichen mit einer bestimmten Farbe ausgegeben werden. Die Verwendung von printf würde diese Färbung entfernen und unlesbare Zeichen drucken. Hier ist die echoeinzige Alternative:

string1=abc
string2=123456
echo -en "$string1 "
for ((i=0; i< (25 - ${#string1}); i++)){ echo -n "-"; }
echo -e " $string2"

Ausgabe:

abc ---------------------- 123456

Natürlich können Sie alle von @Dennis Williamson vorgeschlagenen Variationen verwenden, unabhängig davon, ob der rechte Teil links oder rechts ausgerichtet sein soll ( 25 - ${#string1}durch 25 - ${#string1} - ${#string2}etc ...


2

Hier ist ein anderes:

$ { echo JBoss DOWN; echo GlassFish UP; } | while read PROC STATUS; do echo -n "$PROC "; printf "%$((48-${#PROC}))s " | tr ' ' -; echo " [$STATUS]"; done
JBoss -------------------------------------------- [DOWN]
GlassFish ---------------------------------------- [UP]

2

Wenn Sie die Pad-Zeichen an einer festen Spaltennummer beenden, können Sie sie überfüllen und cutauf Länge bringen:

# Previously defined:
# PROC_NAME
# PROC_STATUS

PAD="--------------------------------------------------"
LINE=$(printf "%s %s" "$PROC_NAME" "$PAD" | cut -c 1-${#PAD})
printf "%s %s\n" "$LINE" "$PROC_STATUS"

2

Einfache Konsolenspanne / Füllung / Pad / Padding mit automatischer Skalierungs- / Größenänderungsmethode und Beispiel.

function create-console-spanner() {
    # 1: left-side-text, 2: right-side-text
    local spanner="";
    eval printf -v spanner \'"%0.1s"\' "-"{1..$[$(tput cols)- 2 - ${#1} - ${#2}]}
    printf "%s %s %s" "$1" "$spanner" "$2";
}

Beispiel: create-console-spanner "loading graphics module" "[success]"

Jetzt hier ist eine voll funktions-Farbe-Zeichen-Terminal-Suite , die mit einem Schraubenschlüssel auf Drucke mit einer Farbe und Stil formatierte String alles in Bezug tut.

# Author: Triston J. Taylor <pc.wiz.tt@gmail.com>
# Date: Friday, October 19th, 2018
# License: OPEN-SOURCE/ANY (NO-PRODUCT-LIABILITY OR WARRANTIES)
# Title: paint.sh
# Description: color character terminal driver/controller/suite

declare -A PAINT=([none]=`tput sgr0` [bold]=`tput bold` [black]=`tput setaf 0` [red]=`tput setaf 1` [green]=`tput setaf 2` [yellow]=`tput setaf 3` [blue]=`tput setaf 4` [magenta]=`tput setaf 5` [cyan]=`tput setaf 6` [white]=`tput setaf 7`);

declare -i PAINT_ACTIVE=1;

function paint-replace() {
    local contents=$(cat)
    echo "${contents//$1/$2}"
}

source <(cat <<EOF
function paint-activate() {
    echo "\$@" | $(for k in ${!PAINT[@]}; do echo -n paint-replace \"\&$k\;\" \"\${PAINT[$k]}\" \|; done) cat;
}
EOF
)

source <(cat <<EOF
function paint-deactivate(){
    echo "\$@" | $(for k in ${!PAINT[@]}; do echo -n paint-replace \"\&$k\;\" \"\" \|; done) cat;    
}
EOF
)

function paint-get-spanner() {
    (( $# == 0 )) && set -- - 0;
    declare -i l=$(( `tput cols` - ${2}))
    eval printf \'"%0.1s"\' "${1:0:1}"{1..$l}
}

function paint-span() {
    local left_format=$1 right_format=$3
    local left_length=$(paint-format -l "$left_format") right_length=$(paint-format -l "$right_format")
    paint-format "$left_format";
    paint-get-spanner "$2" $(( left_length + right_length));
    paint-format "$right_format";
}

function paint-format() {
    local VAR="" OPTIONS='';
    local -i MODE=0 PRINT_FILE=0 PRINT_VAR=1 PRINT_SIZE=2;
    while [[ "${1:0:2}" =~ ^-[vl]$ ]]; do
        if [[ "$1" == "-v" ]]; then OPTIONS=" -v $2"; MODE=$PRINT_VAR; shift 2; continue; fi;
        if [[ "$1" == "-l" ]]; then OPTIONS=" -v VAR"; MODE=$PRINT_SIZE; shift 1; continue; fi;
    done;
    OPTIONS+=" --"
    local format="$1"; shift;
    if (( MODE != PRINT_SIZE && PAINT_ACTIVE )); then
        format=$(paint-activate "$format&none;")
    else
        format=$(paint-deactivate "$format")
    fi
    printf $OPTIONS "${format}" "$@";
    (( MODE == PRINT_SIZE )) && printf "%i\n" "${#VAR}" || true;
}

function paint-show-pallette() {
    local -i PAINT_ACTIVE=1
    paint-format "Normal: &red;red &green;green &blue;blue &magenta;magenta &yellow;yellow &cyan;cyan &white;white &black;black\n";
    paint-format "  Bold: &bold;&red;red &green;green &blue;blue &magenta;magenta &yellow;yellow &cyan;cyan &white;white &black;black\n";
}

Um eine Farbe zu drucken , ist das ganz einfach: paint-format "&red;This is %s\n" red Und vielleicht möchten Sie später fett werden:paint-format "&bold;%s!\n" WOW

Die -lOption für die paint-formatFunktion misst den Text, sodass Sie Konsolenschriftmetrikoperationen ausführen können.

Die -vOption für die paint-formatFunktion funktioniert genauso printf, kann aber nicht bereitgestellt werden-l

Nun zum Spanning !

paint-span "hello " . " &blue;world" [Hinweis: Wir haben keine Newline-Terminalsequenz hinzugefügt, aber der Text füllt das Terminal aus, sodass die nächste Zeile nur eine Newline-Terminalsequenz zu sein scheint.]

und die Ausgabe davon ist:

hello ............................. world


0

Bash + seq, um die Parametererweiterung zu ermöglichen

Ähnlich wie bei der Antwort von @Dennis Williamson, aber falls seqverfügbar, muss die Länge der Pad-Zeichenfolge nicht fest codiert werden. Mit dem folgenden Code können Sie eine Variable als Positionsparameter an das Skript übergeben:

COLUMNS="${COLUMNS:=80}"
padlength="${1:-$COLUMNS}"
pad=$(printf '\x2D%.0s' $(seq "$padlength") )

string2='bbbbbbb'
for string1 in a aa aaaa aaaaaaaa
do
     printf '%s' "$string1"
     printf '%*.*s' 0 $(("$padlength" - "${#string1}" - "${#string2}" )) "$pad"
     printf '%s\n' "$string2"
     string2=${string2:1}
done

Der ASCII-Code "2D" wird anstelle des Zeichens "-" verwendet, um zu vermeiden, dass die Shell ihn als Befehlsflag interpretiert. Eine andere Option ist "3D", um "=" zu verwenden.

In Ermangelung einer als Argument übergebenen Padlength wird im obigen Code standardmäßig die Standard-Terminalbreite von 80 Zeichen verwendet.

Um die Bash-Shell-Variable COLUMNS(dh die Breite des aktuellen Terminals) nutzen zu können, muss die Umgebungsvariable für das Skript verfügbar sein. Eine Möglichkeit besteht darin, alle Umgebungsvariablen durch Ausführen des vorangestellten Skripts .(Befehl "dot") wie folgt zu erstellen:

. /path/to/script

oder (besser) die COLUMNSVariable bei der Ausführung explizit übergeben , wie folgt:

/path/to/script $COLUMNS
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.