Remplissage de caractères dans printf


107

J'écris un script shell bash pour afficher si un processus est en cours d'exécution ou non.

Jusqu'à présent, j'ai ceci:

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

Le code me donne cette sortie:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Je veux combler l'écart entre les deux champs avec un «-» ou un «*» pour le rendre plus lisible. Comment faire cela sans perturber l'alignement des champs?

La sortie que je veux est:

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

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

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

Réponses:


77

Pure Bash, pas d'utilitaires externes

Cette démonstration fait une justification complète, mais vous pouvez simplement omettre de soustraire la longueur de la deuxième chaîne si vous voulez des lignes irrégulières.

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

Malheureusement, dans cette technique, la longueur de la chaîne de pad doit être codée en dur pour être plus longue que la plus longue dont vous pensez avoir besoin, mais la longueur de pad peut être une variable comme indiqué. Cependant, vous pouvez remplacer la première ligne par ces trois pour pouvoir utiliser une variable pour la longueur du pad:

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

Ainsi, le pad ( padlimitet padlength) pourrait être basé sur la largeur du terminal ( $COLUMNS) ou calculé à partir de la longueur de la chaîne de données la plus longue.

Production:

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

Sans soustraire la longueur de la deuxième chaîne:

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

La première ligne pourrait à la place être l'équivalent (similaire à sprintf):

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

ou de même pour la technique plus dynamique:

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

Vous pouvez tout faire l'impression sur une seule ligne si vous préférez:

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

1
Pourriez-vous expliquer un peu la partie printf '% *. * S' ...?
Édouard Lopez

3
@EdouardLopez: Le premier astérisque est remplacé par le zéro dans la liste des arguments. Le deuxième astérisque est remplacé par le résultat du calcul dans le deuxième argument. Le résultat, pour les chaînes "aaaa" et "bbbbb", par exemple, est '%0.31s'. La chaîne (le dernier argument) est tronquée à la longueur spécifiée après le point. Le zéro empêche tout remplissage d'espace d'être sorti. Ainsi, 31 traits d'union sont émis.
Suspendu jusqu'à nouvel ordre.

1
Cette page pourrait aider à comprendre la réponse de @Dennis Williamson: wiki.bash-hackers.org/commands/builtin/printf#modifiers
Édouard Lopez

{1..60} besoin de 60 comme variable; ... comme "var = 60"
Reegan Miranda

@ReeganMiranda: La façon dont cette technique fonctionne est que vous codez en dur la valeur à la plus grande dont vous avez besoin et que vous utilisez padlengthpour sélectionner la longueur réelle à afficher.
Suspendu jusqu'à nouvel ordre.

68

Pure Bash. Utilisez la longueur de la valeur de "PROC_NAME" comme décalage pour la chaîne fixe "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}}"

Cela donne

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

La magie est la ligne $ {: $ {# PROC_NAME}}, qui utilise l'extraction de sous-chaînes bash pour démarrer uniquement le retour à partir d'un point de la ligne variable, qui est définie pour commencer au nombre de caractères dans PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav

Notez que cela ne gère pas le cas où il y PROC_NAMEa des espaces à moins qu'ils ne soient déjà échappés. Vous obtiendrez une ligne avec deux jetons chacun, puis [UP] tous les deux jetons séparés par des espaces dans votre variable, puis une seule ligne à la fin avec votre linetexte moins la longueur totale de votre chaîne d'entrée. Soyez donc prudent, car cela pourrait conduire à des bogues intéressants et potentiellement non sécurisés si cela est fait dans un script complexe. Sinon bref et simple. :)
dodexahedron

19

Solution triviale (mais fonctionnelle):

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

4
Mais uniquement sur un terminal. Si la sortie est envoyée dans un fichier, ce sera un désordre.
thkala

5
alors qu'attendez-vous vraiment d'une solution triviale?!? plein travail également avec la redirection de sortie?!? ]: P
Nicola Leoni

14

Je pense que c'est la solution la plus simple. Builtins shell purs, pas de mathématiques en ligne. Il emprunte aux réponses précédentes.

Juste des sous-chaînes et la méta-variable $ {# ...}.

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"

Produit

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

Produit

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

12

Il n'y a aucun moyen de remplir avec autre chose que des espaces en utilisant printf. Vous pouvez utiliser sed:

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

7
+1 Il y a un problème si PROC_NAME contient un tiret - facilement résolu avec un @ supplémentaire: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]"

Explication:

  • printf '\055%.0s' {1..40}- Créez 40 tirets
    (le tiret est interprété comme une option donc utilisez plutôt le code ascii échappé)
  • "$PROC_NAME ..." - Concaténer $ PROC_NAME et les tirets
  • | head -c 40 - Couper la chaîne aux 40 premiers caractères

Étrange, quand je le fais printf 'x' {1..40}n'imprime qu'un seul xhmmm
Krystian

@Krystian c'est parce que vous n'avez pas copié le format: `printf 'x% .0s' {1..40}` imprime 40 xs
artm

Pour éviter que le tiret soit interprété comme une option, le double tiret peut être utilisé pour signaler que les autres sont des arguments non optionnels printf -- "-%.0s" {1..40}
artm

7

Celui-ci est encore plus simple et n'exécute aucune commande externe.

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

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

5

Simple mais ça marche:

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

Exemple d'utilisation:

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

Sortie vers stdout:

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

4

en utilisant echouniquement

La réponse de @Dennis Williamson fonctionne très bien sauf que j'essayais de le faire en utilisant echo. Echo permet de sortir des caractères avec une certaine couleur. L'utilisation de printf supprimerait cette coloration et imprimerait des caractères illisibles. Voici la echoseule alternative:

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

production:

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

bien sûr, vous pouvez utiliser toutes les variantes proposées par @Dennis Williamson que vous souhaitiez que la partie droite soit alignée à gauche ou à droite (en remplaçant 25 - ${#string1}par 25 - ${#string1} - ${#string2}etc ...


2

En voici un autre:

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

Si vous terminez les caractères du pad à un numéro de colonne fixe, vous pouvez surcharger et cutà la longueur:

# 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

Simple Console Span / Fill / Pad / Padding avec méthode de mise à l'échelle / redimensionnement automatique et exemple.

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

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

Voici maintenant une suite complète de terminaux de caractères couleur qui fait tout ce qui concerne l'impression d'une chaîne formatée en couleur et en style avec une clé.

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

Pour imprimer une couleur , c'est assez simple: paint-format "&red;This is %s\n" red et vous voudrez peut-être devenir gras plus tard:paint-format "&bold;%s!\n" WOW

L' -loption de la paint-formatfonction mesure le texte afin que vous puissiez effectuer des opérations de métrique de police de la console.

L' -voption de la paint-formatfonction fonctionne de la même manière printfmais ne peut pas être fournie avec-l

Maintenant pour le spanning !

paint-span "hello " . " &blue;world" [note: nous n'avons pas ajouté de séquence de terminal de nouvelle ligne, mais le texte remplit le terminal, donc la ligne suivante ne semble être qu'une séquence de terminal de nouvelle ligne]

et le résultat est:

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


0

Bash + seq pour permettre l'expansion des paramètres

Similaire à la réponse @Dennis Williamson, mais si elle seqest disponible, la longueur de la chaîne de remplissage n'a pas besoin d'être codée en dur. Le code suivant permet de transmettre une variable au script en tant que paramètre de position:

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

Le code ASCII "2D" est utilisé à la place du caractère "-" pour éviter que le shell ne l'interprète comme un drapeau de commande. Une autre option est "3D" pour utiliser "=".

En l'absence de toute longueur de pad passée en argument, le code ci-dessus utilise par défaut la largeur de terminal standard de 80 caractères.

Pour tirer parti de la variable shell bash COLUMNS(c'est-à-dire la largeur du terminal actuel), la variable d'environnement doit être disponible pour le script. Une façon est de rechercher toutes les variables d'environnement en exécutant le script précédé de .(commande "point"), comme ceci:

. /path/to/script

ou (mieux) transmettre explicitement la COLUMNSvariable lors de l'exécution, comme ceci:

/path/to/script $COLUMNS
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.