Wypełnienie znaków w printf

107

Piszę skrypt powłoki bash do wyświetlenia, czy proces jest uruchomiony, czy nie.

Do tej pory mam to:

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

Kod daje mi takie wyjście:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Chcę wypełnić lukę między dwoma polami za pomocą „-” lub „*”, aby była bardziej czytelna. Jak to zrobić bez zakłócania wyrównania pól?

Wynik, którego chcę, to:

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

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

verylongprocessname -----------------------------  [UP]
serdeczny
źródło

Odpowiedzi:

77

Pure Bash, bez zewnętrznych narzędzi

Ta demonstracja ma pełne uzasadnienie, ale możesz po prostu pominąć odejmowanie długości drugiego ciągu, jeśli chcesz, aby linie były nierówne.

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

Niestety, w tej technice długość struny pada musi być zakodowana na stałe, aby była dłuższa niż najdłuższa, której myślisz, że będziesz potrzebować, ale długość padu może być zmienną, jak pokazano. Możesz jednak zastąpić pierwszą linię tymi trzema, aby móc użyć zmiennej dla długości pada:

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

Zatem pad ( padlimiti padlength) może być oparty na szerokości terminala ( $COLUMNS) lub obliczony na podstawie długości najdłuższego ciągu danych.

Wynik:

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

Bez odejmowania długości drugiej struny:

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

Pierwsza linia mogłaby być odpowiednikiem (podobnym do sprintf):

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

lub podobnie dla bardziej dynamicznej techniki:

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

Jeśli wolisz, możesz wydrukować wszystko w jednej linii:

printf '%s%*.*s%s\n' "$string1" 0 $((padlength - ${#string1} - ${#string2} )) "$pad" "$string2"
Wstrzymano do odwołania.
źródło
1
Czy mógłbyś trochę wyjaśnić część printf '% *. * S' ...?
Édouard Lopez
3
@EdouardLopez: pierwsza gwiazdka jest zastępowana przez zero na liście argumentów. Drugą gwiazdkę zastępuje wynik obliczenia w drugim argumencie. Na przykład wynik dla ciągów „aaaa” i „bbbbb” to '%0.31s'. Łańcuch (ostatni argument) jest obcinany do długości określonej po kropce. Zero zapobiega wyprowadzaniu wypełnienia spacji. Wyprowadzanych jest więc 31 łączników.
Wstrzymano do odwołania.
1
Ta strona może pomóc zrozumieć odpowiedź @Dennis Williamson: wiki.bash-hackers.org/commands/builtin/printf#modifiers
Édouard Lopez
{1..60} potrzebuje 60 jako zmienna; ... na przykład „var = 60”
Reegan Miranda
@ReeganMiranda: Sposób, w jaki ta technika działa, polega na tym, że zakodujesz wartość na największą, której potrzebujesz, i używasz padlengthdo wybrania rzeczywistej długości do wydrukowania.
Wstrzymano do odwołania.
68

Pure Bash. Użyj długości wartości „PROC_NAME” jako przesunięcia dla ustalonego ciągu „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}}"

To daje

abc ------------------------------------- [UP]
abcdef ---------------------------------- [UP]
Fritz G. Mehner
źródło
Magią jest linia $ {: $ {# PROC_NAME}}, która używa ekstrakcji podłańcucha basha, aby rozpocząć powrót tylko od punktu zmiennej linii, która jest ustawiona tak, aby rozpoczynać się od liczby znaków w PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav Kwietnia
Zauważ, że to nie obsługuje przypadku, w którym PROC_NAMEsą spacje, chyba że zostały już zmienione . Otrzymasz jeden wiersz z dwoma tokenami każdy, a następnie [UP] na każde dwa oddzielone spacjami tokeny w zmiennej, a następnie jeden wiersz na końcu zawierający linetekst minus całkowita długość ciągu wejściowego. Dlatego bądź ostrożny, ponieważ może to prowadzić do interesujących i potencjalnie niebezpiecznych błędów, jeśli zostanie wykonane w złożonym skrypcie. W przeciwnym razie krótkie i proste. :)
dodexahedron
19

Trywialne (ale działające) rozwiązanie:

echo -e "---------------------------- [UP]\r$PROC_NAME "
Nicola Leoni
źródło
4
Ale tylko na terminalu. Jeśli wynik zostanie wysłany do pliku, będzie bałagan.
thkala
5
więc czego naprawdę oczekujesz od trywialnego rozwiązania?!? pełna praca również z przekierowaniem wyjścia?!? ]: P
Nicola Leoni
14

Myślę, że to najprostsze rozwiązanie. Czysta wbudowana powłoka, bez wbudowanej matematyki. Pożycza z poprzednich odpowiedzi.

Tylko podciągi i metazmienna $ {# ...}.

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"

Produkuje

[>----- 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"

Produkuje

-----<] A very long header
---------------<] shrt hdr
synthesizerpatel
źródło
12

Nie ma sposobu na wypełnienie czymkolwiek poza użyciem spacji printf. Możesz użyć sed:

printf "%-50s@%s\n" $PROC_NAME [UP] | sed -e 's/ /-/g' -e 's/@/ /' -e 's/-/ /'
F'x
źródło
7
+1 Występuje problem, jeśli PROC_NAME zawiera myślnik - można go łatwo rozwiązać za pomocą dodatkowego @: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]"

Wyjaśnienie:

  • printf '\055%.0s' {1..40}- Utwórz 40 myślników
    (myślnik jest interpretowany jako opcja, więc zamiast tego użyj kodu ascii ze znakiem ucieczki)
  • "$PROC_NAME ..." - Połącz $ PROC_NAME i myślniki
  • | head -c 40 - Przytnij ciąg do pierwszych 40 znaków
draganHR
źródło
Dziwne, jak printf 'x' {1..40}to robię , drukuje tylko pojedyncze xhmmm
Krystian
@Krystian to dlatego, że nie skopiowałeś formatu: `printf 'x% .0s' {1..40}` drukuje 40 xs
artm
Aby uniknąć interpretacji myślnika jako opcji, można użyć podwójnego myślnika, aby zasygnalizować, że reszta to argumenty nie będące opcjamiprintf -- "-%.0s" {1..40}
artm
7

Ten jest jeszcze prostszy i nie wykonuje żadnych zewnętrznych poleceń.

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

JBoss............... [UP]
Chad Juliano
źródło
5

Proste, ale działa:

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

Przykład użycia:

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

Wyjście na stdout:

JBoss -------------------------------------------- [DOWN]
GlassFish ---------------------------------------- [UP]
VeryLongProcessName ------------------------------ [UP]
Luis Daniel
źródło
4

używając echotylko

Odpowiedź @Dennis Williamson działa dobrze, z wyjątkiem tego, że próbowałem to zrobić za pomocą echa. Echo pozwala na wyświetlanie znaków o określonym kolorze. Używanie printf usuwałoby to zabarwienie i drukowało nieczytelne znaki. Oto echojedyna alternatywa:

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

wynik:

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

oczywiście możesz użyć wszystkich wariantów zaproponowanych przez @Dennis Williamson, niezależnie od tego, czy chcesz, aby prawa część była wyrównana do lewej lub prawej (zastąpiona 25 - ${#string1}przez 25 - ${#string1} - ${#string2}itp ...

Chris Maes
źródło
2

Oto kolejny:

$ { 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]
thkala
źródło
2

Jeśli kończysz znaki padów na jakimś stałym numerze kolumny, możesz przesadzić i wydłużyć cut:

# 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"
Gurn
źródło
2

Prosty zakres / wypełnienie / wypełnienie / dopełnienie konsoli z automatycznym skalowaniem / metodą zmiany rozmiaru i przykładem.

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

Przykład: create-console-spanner "loading graphics module" "[success]"

Teraz mamy w pełni funkcjonalny zestaw terminali z kolorowymi znakami, który robi wszystko, co dotyczy drukowania za pomocą klucza łańcuchowego sformatowanego w kolorze i stylu.

# Author: Triston J. Taylor <[email protected]>
# 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";
}

Aby wydrukować kolor , jest to dość proste: paint-format "&red;This is %s\n" red a później możesz chcieć pogrubić:paint-format "&bold;%s!\n" WOW

-lOpcja z paint-formatfunkcją pomiaru tekst, dzięki czemu można zrobić operacje parametrów czcionki konsola.

-vOpcja z paint-formatfunkcją działa tak samo jak printf, ale nie może być dostarczony z-l

Teraz czas na rozpiętość !

paint-span "hello " . " &blue;world" [uwaga: nie dodaliśmy sekwencji terminala nowej linii, ale tekst wypełnia terminal, więc następna linia wydaje się być tylko sekwencją terminala nowej linii]

a wynikiem tego jest:

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

Systemy Hypersoft
źródło
0

Bash + seq, aby umożliwić rozszerzanie parametrów

Podobnie jak odpowiedź @Dennis Williamson, ale jeśli seqjest dostępna, długość ciągu padu nie musi być zakodowana na stałe. Poniższy kod umożliwia przekazanie zmiennej do skryptu jako parametru pozycyjnego:

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

Kod ASCII „2D” jest używany zamiast znaku „-”, aby uniknąć interpretowania go przez powłokę jako flagi polecenia. Inną opcją jest „3D” do użycia „=”.

W przypadku braku jakiejkolwiek długości dopełnienia przekazanej jako argument, powyższy kod domyślnie przyjmuje standardową szerokość terminala 80 znaków.

Aby skorzystać ze zmiennej powłoki bash COLUMNS(tj. Szerokości bieżącego terminala), zmienna środowiskowa musiałaby być dostępna dla skryptu. Jednym ze sposobów jest pozyskanie wszystkich zmiennych środowiskowych poprzez wykonanie skryptu poprzedzonego .(polecenie „kropka”), na przykład:

. /path/to/script

lub (lepiej) jawnie przekaż COLUMNSzmienną podczas wykonywania, na przykład:

/path/to/script $COLUMNS
Loye Young
źródło