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]

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"

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]

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 "

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

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/-/ /'

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

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]

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]

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 ...


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]

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"

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

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.