Caracteres de preenchimento em printf

107

Estou escrevendo um script de shell bash para exibir se um processo está em execução ou não.

Até agora, eu entendi:

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

O código me dá esta saída:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Quero preencher a lacuna entre os dois campos com '-' ou '*' para torná-lo mais legível. Como faço isso sem perturbar o alinhamento dos campos?

A saída que desejo é:

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

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

verylongprocessname -----------------------------  [UP]
cordão
fonte

Respostas:

77

Pure Bash, sem utilitários externos

Esta demonstração justifica totalmente, mas você pode simplesmente omitir a subtração do comprimento da segunda string se desejar linhas retas irregulares.

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

Infelizmente, nessa técnica, o comprimento da corda da almofada deve ser codificado para ser maior do que a mais longa que você acha que vai precisar, mas o comprimento da almofada pode ser uma variável, conforme mostrado. No entanto, você pode substituir a primeira linha por estas três para poder usar uma variável para o comprimento do bloco:

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

Portanto, o pad ( padlimite padlength) pode ser baseado na largura do terminal ( $COLUMNS) ou calculado a partir do comprimento da string de dados mais longa.

Resultado:

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

Sem subtrair o comprimento da segunda string:

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

A primeira linha pode ser equivalente (semelhante a sprintf):

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

ou da mesma forma para a técnica mais dinâmica:

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

Você pode imprimir tudo em uma linha, se preferir:

printf '%s%*.*s%s\n' "$string1" 0 $((padlength - ${#string1} - ${#string2} )) "$pad" "$string2"
Pausado até novo aviso.
fonte
1
Você poderia explicar um pouco a parte printf '% *. * S' ...?
Édouard Lopez
3
@EdouardLopez: O primeiro asterisco é substituído pelo zero na lista de argumentos. O segundo asterisco é substituído pelo resultado do cálculo no segundo argumento. O resultado, para as strings "aaaa" e "bbbbb", por exemplo, é '%0.31s'. A string (o argumento final) é truncada para o comprimento especificado após o ponto. O zero evita que qualquer preenchimento de espaço seja gerado. Assim, 31 hífens são produzidos.
Pausado até novo aviso.
1
Esta página pode ajudar a entender a resposta de @Dennis
Édouard Lopez
{1..60} precisa de 60 como variável; ... como "var = 60"
Reegan Miranda em
@ReeganMiranda: A maneira como essa técnica funciona é que você codifica permanentemente o valor para o maior que você precisa e usa padlengthpara selecionar o comprimento real para a saída.
Pausado até novo aviso.
68

Pure Bash. Use o comprimento do valor de 'PROC_NAME' como deslocamento para a string fixa '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}}"

Isto dá

abc ------------------------------------- [UP]
abcdef ---------------------------------- [UP]
Fritz G. Mehner
fonte
A mágica é a $ {line: $ {# PROC_NAME}}, que usa a extração de substring bash para iniciar o retorno apenas de um ponto da linha variável, que é configurada para iniciar no número de caracteres em PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav
Observe que isso não trata do caso em que PROC_NAMEtem espaços, a menos que já tenham escapado. Você obterá uma linha com dois tokens cada e depois [UP] para cada dois tokens separados por espaço em sua variável e, em seguida, uma única linha no final com seu linetexto menos o comprimento total de sua string de entrada. Portanto, tome cuidado, pois isso pode levar a bugs interessantes e potencialmente inseguros se feito em um script complexo. Caso contrário, curto e simples. :)
dodexaedro
19

Solução trivial (mas funcionando):

echo -e "---------------------------- [UP]\r$PROC_NAME "
Nicola Leoni
fonte
4
Mas apenas em um terminal. Se a saída for enviada para um arquivo, será uma bagunça.
thkala de
5
então o que você realmente espera de uma solução trivial?!? completo funcionando também com redirecionamento de saída?!? ]: P
Nicola Leoni
14

Acho que essa é a solução mais simples. Construções de shell puras, sem matemática embutida. Ele pega emprestado de respostas anteriores.

Apenas substrings e a metavariável $ {# ...}.

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"

Produz

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

Produz

-----<] A very long header
---------------<] shrt hdr
synthesizerpatel
fonte
12

Não há como preencher com nada além de espaços usando printf. Você pode usar sed:

printf "%-50s@%s\n" $PROC_NAME [UP] | sed -e 's/ /-/g' -e 's/@/ /' -e 's/-/ /'
F'x
fonte
7
+1 Há um problema se PROC_NAME contiver um travessão - facilmente resolvido com um adicional @: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]"

Explicação:

  • printf '\055%.0s' {1..40}- Crie 40 traços
    (o traço é interpretado como uma opção, portanto, use o código ASCII de escape)
  • "$PROC_NAME ..." - Concatene $ PROC_NAME e travessões
  • | head -c 40 - Corte a string para os primeiros 40 caracteres
draganHR
fonte
Estranho, quando faço printf 'x' {1..40}isso apenas imprime uma única xhmmm
Krystian
@Krystian é porque você não copiou o formato: `printf 'x% .0s' {1..40}` imprime 40 xs
artm
Para evitar que o traço seja interpretado como opção, o traço duplo pode ser usado para sinalizar que o resto são argumentos não opcionaisprintf -- "-%.0s" {1..40}
artm
7

Este é ainda mais simples e não executa comandos externos.

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

JBoss............... [UP]
Chad Juliano
fonte
5

Simples, mas funciona:

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

Exemplo de uso:

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

Saída para stdout:

JBoss -------------------------------------------- [DOWN]
GlassFish ---------------------------------------- [UP]
VeryLongProcessName ------------------------------ [UP]
Luis daniel
fonte
4

usando echoapenas

O anwser de @Dennis Williamson está funcionando muito bem, exceto que eu estava tentando fazer isso usando eco. Echo permite a saída de caracteres com uma determinada cor. Usar printf removeria essa coloração e imprimiria caracteres ilegíveis. Esta é a echoúnica alternativa:

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

resultado:

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

é claro que você pode usar todas as variações propostas por @Dennis Williamson se quiser que a parte direita seja alinhada à esquerda ou à direita (substituindo 25 - ${#string1}por 25 - ${#string1} - ${#string2}etc ...

Chris Maes
fonte
2

Aqui está mais um:

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

Se você está terminando os caracteres de preenchimento em algum número de coluna fixo, você pode preenchê-los cutcom o comprimento:

# 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
fonte
2

Console simples Span / Fill / Pad / Padding com escalonamento / redimensionamento automático Método e Exemplo.

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

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

Agora, aqui está um conjunto completo de terminais de caracteres coloridos que faz tudo em relação à impressão de uma string formatada em cores e estilos com uma chave inglesa.

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

Para imprimir uma cor , isso é bastante simples: paint-format "&red;This is %s\n" red E você pode querer ousar mais tarde:paint-format "&bold;%s!\n" WOW

A -lopção para a paint-formatfunção mede o texto para que você possa fazer operações de métricas de fonte do console.

A -vopção para a paint-formatfunção funciona da mesma forma, printfmas não pode ser fornecida com-l

Agora, para a abrangência !

paint-span "hello " . " &blue;world" [observação: não adicionamos sequência de terminal de nova linha, mas o texto preenche o terminal, então a próxima linha parece ser apenas uma sequência de terminal de nova linha]

e o resultado disso é:

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

Hypersoft Systems
fonte
0

Bash + seq para permitir a expansão do parâmetro

Semelhante à resposta de @Dennis Williamson, mas se seqestiver disponível, o comprimento da corda do bloco não precisa ser codificado. O código a seguir permite passar uma variável para o script como um parâmetro posicional:

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

O código ASCII "2D" é usado em vez do caractere "-" para evitar que o shell o interprete como um sinalizador de comando. Outra opção é "3D" para usar "=".

Na ausência de qualquer padlength passado como um argumento, o código acima é padronizado para a largura de terminal padrão de 80 caracteres.

Para tirar proveito da variável bash shell COLUMNS(ou seja, a largura do terminal atual), a variável de ambiente precisa estar disponível para o script. Uma maneira é originar todas as variáveis ​​de ambiente executando o script precedido por .(comando "ponto"), assim:

. /path/to/script

ou (melhor) passar explicitamente a COLUMNSvariável ao executar, assim:

/path/to/script $COLUMNS
Loye Young
fonte