Relleno de caracteres en printf


107

Estoy escribiendo un script de shell bash para mostrar si un proceso se está ejecutando o no.

Hasta ahora, tengo esto:

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

El código me da esta salida:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Quiero rellenar el espacio entre los dos campos con un '-' o '*' para hacerlo más legible. ¿Cómo hago eso sin alterar la alineación de los campos?

La salida que quiero es:

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

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

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

Respuestas:


77

Pure Bash, sin utilidades externas

Esta demostración tiene una justificación completa, pero puede omitir restar la longitud de la segunda cadena si desea líneas irregulares a la derecha.

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

Desafortunadamente, en esa técnica, la longitud de la cuerda de la almohadilla debe estar codificada para que sea más larga que la más larga que cree que necesitará, pero la longitud de la almohadilla puede ser una variable como se muestra. Sin embargo, puede reemplazar la primera línea con estas tres para poder usar una variable para la longitud del pad:

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

Entonces, el pad ( padlimity padlength) podría basarse en el ancho de la terminal ( $COLUMNS) o calcularse a partir de la longitud de la cadena de datos más larga.

Salida:

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

Sin restar la longitud de la segunda cadena:

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

En cambio, la primera línea podría ser el equivalente (similar a sprintf):

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

o similar para la técnica más dinámica:

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

Puede imprimir todo en una línea si lo prefiere:

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

1
¿Podría explicar un poco la parte printf '% *. * S' ...?
Édouard Lopez

3
@EdouardLopez: El primer asterisco se reemplaza por el cero en la lista de argumentos. El segundo asterisco se reemplaza por el resultado del cálculo en el segundo argumento. El resultado, para las cadenas "aaaa" y "bbbbb", por ejemplo, es '%0.31s'. La cadena (el argumento final) se trunca a la longitud especificada después del punto. El cero evita que se genere cualquier relleno de espacio. Por tanto, se emiten 31 guiones.
Pausado hasta nuevo aviso.

1
Esta página podría ayudar a comprender la respuesta de @Dennis Williamson: wiki.bash-hackers.org/commands/builtin/printf#modifiers
Édouard Lopez

{1..60} necesita 60 como variable; ... como "var = 60"
Reegan Miranda

@ReeganMiranda: La forma en que funciona esta técnica es que codificas el valor al más grande que necesitas y lo usas padlengthpara seleccionar la longitud real a la salida.
Pausado hasta nuevo aviso.

68

Pure Bash. Utilice la longitud del valor de 'PROC_NAME' como compensación para la cadena fija 'línea':

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

Esto da

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

La magia es $ {line: $ {# PROC_NAME}}, que usa la extracción de subcadenas bash para comenzar a retornar solo desde un punto de la línea variable, que está configurada para comenzar en el número de caracteres en PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav

Tenga en cuenta que esto no maneja el caso donde PROC_NAMEtiene espacios a menos que ya se hayan escapado. Obtendrá una línea con dos tokens cada uno y luego [ARRIBA] por cada dos tokens separados por espacios en su variable y luego una sola línea al final con su linetexto menos la longitud total de su cadena de entrada. Así que tenga cuidado, ya que esto podría generar errores interesantes y potencialmente inseguros si se hace en un script complejo. De lo contrario, breve y simple. :)
dodexaedro

19

Solución trivial (pero funcional):

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

4
Pero solo en una terminal. Si la salida se envía a un archivo, será un desastre.
thkala

5
entonces, ¿qué esperas realmente de una solución trivial? funcionamiento completo también con redirección de salida?!? ]: P
Nicola Leoni

14

Creo que esta es la solución más sencilla. Incorporaciones de shell puro, sin matemáticas en línea. Toma prestado de respuestas anteriores.

Solo subcadenas y la metavariable $ {# ...}.

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

No hay forma de rellenar con nada más que espacios usando printf. Puede utilizar sed:

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

7
+1 Hay un problema si PROC_NAME contiene un guión; se resuelve fácilmente con un @:printf "%-50s@%s\n" ${PROC_NAME}@ [UP] | sed -e 's/ /-/g' -e 's/-@/ /' -e 's/@-/ /'
thkala adicional

9
echo -n "$PROC_NAME $(printf '\055%.0s' {1..40})" | head -c 40 ; echo -n " [UP]"

Explicación:

  • printf '\055%.0s' {1..40}- Cree 40 guiones
    (el guión se interpreta como una opción, así que use código ascii de escape en su lugar)
  • "$PROC_NAME ..." - Concatenar $ PROC_NAME y guiones
  • | head -c 40 - Recorte la cadena a los primeros 40 caracteres

Extraño, cuando lo hago printf 'x' {1..40}solo imprime una xhmmm
Krystian

@Krystian, eso se debe a que no ha copiado el formato: `printf 'x% .0s' {1..40}` imprime 40 xs
artm

Para evitar que el guión se interprete como una opción, el guión doble se puede usar para indicar que el resto son argumentos que no son de opciónprintf -- "-%.0s" {1..40}
artm

7

Este es aún más simple y no tiene comandos externos.

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

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

5

Simple pero funciona:

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

Ejemplo 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

Salida a stdout:

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

4

usando echosolo

La respuesta de @Dennis Williamson está funcionando bien, excepto que estaba tratando de hacer esto usando echo. Echo permite generar caracteres con un color determinado. El uso de printf eliminaría ese color e imprimiría caracteres ilegibles. Aquí está la echoúnica alternativa:

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

salida:

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

por supuesto, puede usar todas las variaciones propuestas por @Dennis Williamson si desea que la parte derecha esté alineada a la izquierda o a la derecha (reemplazando 25 - ${#string1}por 25 - ${#string1} - ${#string2}etc ...


2

Aqui hay otro más:

$ { 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 está terminando los caracteres del pad en algún número de columna fijo, entonces puede overpad y cuta la longitud:

# 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

Consola simple Span / Fill / Pad / Padding con método y ejemplo de escalado / cambio de tamaño automático.

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

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

Ahora aquí hay una suite de terminales de caracteres de color con todas las funciones que hace todo lo que se refiere a imprimir una cadena con formato de color y estilo con una llave inglesa.

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

Para imprimir un color , eso es bastante simple: paint-format "&red;This is %s\n" red y es posible que desee ponerse en negrita más adelante:paint-format "&bold;%s!\n" WOW

La -lopción de la paint-formatfunción mide el texto para que pueda realizar operaciones de métricas de fuente de consola.

La -vopción de la paint-formatfunción funciona igual que printfpero no se puede suministrar con-l

¡Ahora para la expansión !

paint-span "hello " . " &blue;world" [nota: no agregamos una secuencia de terminal de nueva línea, pero el texto llena la terminal, por lo que la siguiente línea solo parece ser una secuencia de terminal de nueva línea]

y el resultado de eso es:

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


0

Bash + seq para permitir la expansión de parámetros

Similar a la respuesta de @Dennis Williamson, pero si seqestá disponible, la longitud de la cadena del pad no necesita estar codificada. El siguiente código permite pasar una variable al script como 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

Se utiliza el código ASCII "2D" en lugar del carácter "-" para evitar que el shell lo interprete como una bandera de comando. Otra opción es "3D" para usar "=".

En ausencia de cualquier longitud de padlength pasada como argumento, el código anterior tiene como valor predeterminado el ancho de terminal estándar de 80 caracteres.

Para aprovechar la variable de shell bash COLUMNS(es decir, el ancho de la terminal actual), la variable de entorno debería estar disponible para el script. Una forma es obtener todas las variables de entorno ejecutando el script precedido por .(comando "punto"), así:

. /path/to/script

o (mejor) pasar explícitamente la COLUMNSvariable al ejecutar, así:

/path/to/script $COLUMNS
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.