Caratteri di imbottitura in printf


107

Sto scrivendo uno script di shell bash per visualizzare se un processo è in esecuzione o meno.

Finora ho capito:

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

Il codice mi dà questo output:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Voglio riempire lo spazio tra i due campi con un "-" o "*" per renderlo più leggibile. Come posso farlo senza disturbare l'allineamento dei campi?

L'output che voglio è:

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

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

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

Risposte:


77

Pure Bash, nessuna utilità esterna

Questa dimostrazione fornisce una giustificazione completa, ma puoi semplicemente omettere di sottrarre la lunghezza della seconda stringa se vuoi linee irregolari.

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

Sfortunatamente, in quella tecnica, la lunghezza della stringa del pad deve essere codificata per essere più lunga di quella più lunga di cui pensi di aver bisogno, ma la lunghezza del pad può essere una variabile come mostrato. Tuttavia, puoi sostituire la prima riga con queste tre per poter utilizzare una variabile per la lunghezza del pad:

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

Quindi il pad ( padlimite padlength) potrebbe essere basato sulla larghezza del terminale ( $COLUMNS) o calcolato dalla lunghezza della stringa di dati più lunga.

Produzione:

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

Senza sottrarre la lunghezza della seconda stringa:

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

La prima riga potrebbe invece essere l'equivalente (simile a sprintf):

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

o similmente per la tecnica più dinamica:

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

Puoi stampare tutto su una riga se preferisci:

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

1
Potresti spiegare un po 'la parte printf'% *. * S '...?
Édouard Lopez

3
@EdouardLopez: il primo asterisco è sostituito dallo zero nell'elenco degli argomenti. Il secondo asterisco viene sostituito dal risultato del calcolo nel secondo argomento. Il risultato, ad esempio, per le stringhe "aaaa" e "bbbbb" è '%0.31s'. La stringa (l'argomento finale) viene troncata alla lunghezza specificata dopo il punto. Lo zero impedisce l'output di qualsiasi riempimento di spazio. Quindi vengono emessi 31 trattini.
In pausa fino a nuovo avviso.

1
Questa pagina potrebbe aiutare a capire la risposta di @Dennis Williamson: wiki.bash-hackers.org/commands/builtin/printf#modifiers
Édouard Lopez

{1..60} necessita di 60 come variabile; ... come "var = 60"
Reegan Miranda

@ReeganMiranda: Il modo in cui funziona questa tecnica è che inserisci il valore più grande di cui hai bisogno e lo usi padlengthper selezionare la lunghezza effettiva da stampare.
In pausa fino a nuovo avviso.

68

Pure Bash. Utilizza la lunghezza del valore di "PROC_NAME" come offset per la stringa fissa "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}}"

Questo da

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

La magia è la $ {riga: $ {# PROC_NAME}}, che utilizza l'estrazione della sottostringa bash per avviare il ritorno solo da un punto della riga variabile, che è impostata per iniziare dal numero di caratteri in PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav

Nota che questo non gestisce il caso in cui PROC_NAMEha spazi a meno che non siano già stati sottoposti a escape. Otterrai una riga con due token ciascuno e poi [UP] per ogni due token separati da spazi nella tua variabile e poi una singola riga alla fine con il tuo linetesto meno la lunghezza totale della stringa di input. Quindi fai attenzione, poiché questo potrebbe portare a bug interessanti e potenzialmente insicuri se fatto in uno script complesso. Altrimenti breve e semplice. :)
dodexahedron

19

Soluzione banale (ma funzionante):

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

4
Ma solo su un terminale. Se l'output viene inviato a un file, sarà un pasticcio.
thkala

5
quindi cosa ti aspetti veramente da una banale soluzione?!? perfettamente funzionante anche con reindirizzamento dell'output?!? ]: P
Nicola Leoni

14

Penso che questa sia la soluzione più semplice. Integrati shell puri, nessuna matematica in linea. Prende in prestito dalle risposte precedenti.

Solo sottostringhe e la meta-variabile $ {# ...}.

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"

produce

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

produce

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

12

Non c'è modo di riempire altro che spazi utilizzando printf. Puoi usare sed:

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

7
+1 C'è un problema se PROC_NAME contiene un trattino - facilmente risolvibile con un @: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]"

Spiegazione:

  • printf '\055%.0s' {1..40}- Crea 40 trattini
    (il trattino viene interpretato come un'opzione, quindi usa il codice ASCII con escape)
  • "$PROC_NAME ..." - Concatena $ PROC_NAME e i trattini
  • | head -c 40 - Taglia la stringa ai primi 40 caratteri

Strano, quando lo faccio printf 'x' {1..40}stampa solo un singolo xhmmm
Krystian

@Krystian è perché non hai copiato il formato: `printf 'x% .0s' {1..40}` stampa 40 xs
artm

Per evitare che il trattino venga interpretato come opzione, è possibile utilizzare il doppio trattino per segnalare che gli altri sono argomenti non opzionaliprintf -- "-%.0s" {1..40}
artm

7

Questo è ancora più semplice e non esegue comandi esterni.

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

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

5

Semplice ma funziona:

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

Esempio di utilizzo:

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

Uscita su stdout:

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

4

usando echosolo

La risposta di @Dennis Williamson funziona perfettamente tranne che stavo cercando di farlo usando l'eco. Echo consente di emettere caratteri con un certo colore. L'uso di printf rimuove quel colore e stampa caratteri illeggibili. Ecco l' echounica alternativa:

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

produzione:

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

ovviamente puoi utilizzare tutte le varianti proposte da @Dennis Williamson sia che tu voglia che la parte destra sia allineata a sinistra oa destra (sostituendo 25 - ${#string1}con 25 - ${#string1} - ${#string2}ecc ...


2

Eccone un altro:

$ { 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

Se stai terminando i caratteri del pad in un numero di colonna fisso, puoi sovrapporre e cutallungare:

# 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

Semplice Console Span / Fill / Pad / Padding con metodo di ridimensionamento / ridimensionamento automatico ed esempio.

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

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

Ora ecco una suite di terminali con caratteri a colori completa che fa tutto ciò che riguarda la stampa di una stringa formattata con colori e stile con una chiave inglese.

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

Per stampare un colore , è abbastanza semplice: paint-format "&red;This is %s\n" red e potresti voler diventare audace in seguito:paint-format "&bold;%s!\n" WOW

L' -lopzione della paint-formatfunzione misura il testo in modo da poter eseguire le operazioni di metrica dei caratteri della console.

L' -vopzione per la paint-formatfunzione funziona allo stesso modo diprintf ma non può essere fornita con-l

Ora per lo spanning !

paint-span "hello " . " &blue;world" [nota: non abbiamo aggiunto la sequenza del terminale di nuova riga, ma il testo riempie il terminale, quindi la riga successiva sembra essere solo una sequenza di terminale di nuova riga]

e il risultato è:

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


0

Bash + seq per consentire l'espansione dei parametri

Simile alla risposta di @Dennis Williamson, ma se seqè disponibile, la lunghezza della stringa del pad non deve essere codificata. Il codice seguente consente di passare una variabile allo script come parametro posizionale:

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

Il codice ASCII "2D" viene utilizzato al posto del carattere "-" per evitare che la shell lo interpreti come un flag di comando. Un'altra opzione è "3D" per utilizzare "=".

In assenza di una lunghezza pad passata come argomento, il codice precedente utilizza per impostazione predefinita la larghezza del terminale standard di 80 caratteri.

Per sfruttare la variabile della shell bash COLUMNS(cioè, la larghezza del terminale corrente), la variabile d'ambiente dovrebbe essere disponibile per lo script. Un modo è quello di generare tutte le variabili d'ambiente eseguendo lo script preceduto da .(comando "punto"), in questo modo:

. /path/to/script

o (meglio) passare esplicitamente la COLUMNSvariabile durante l'esecuzione, in questo modo:

/path/to/script $COLUMNS
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.