Dibujar un histograma de una salida de comando bash

31

Tengo el siguiente resultado:

2015/1/7    8
2015/1/8    49
2015/1/9    40
2015/1/10   337
2015/1/11   11
2015/1/12   3
2015/1/13   9
2015/1/14   102
2015/1/15   62
2015/1/16   10
2015/1/17   30
2015/1/18   30
2015/1/19   1
2015/1/20   3
2015/1/21   23
2015/1/22   12
2015/1/24   6
2015/1/25   3
2015/1/27   2
2015/1/28   16
2015/1/29   1
2015/2/1    12
2015/2/2    2
2015/2/3    1
2015/2/4    10
2015/2/5    13
2015/2/6    2
2015/2/9    2
2015/2/10   25
2015/2/11   1
2015/2/12   6
2015/2/13   12
2015/2/14   2
2015/2/16   8
2015/2/17   8
2015/2/20   1
2015/2/23   1
2015/2/27   1
2015/3/2    3
2015/3/3    2

Y me gustaría dibujar un histograma

2015/1/7  ===
2015/1/8  ===========
2015/1/9  ==========
2015/1/10 ====================================================================
2015/1/11 ===
2015/1/11 =
...

¿Sabes si hay un comando bash que me permita hacer eso?

Natim
fuente
1
bashplotlib es una buena solución
Michael Mior
Ese es, de hecho, uno de los riesgos de proporcionar enlaces en lugar de respuestas independientes. Si la respuesta SO eliminada es útil, publíquela aquí como respuesta.
Jeff Schaller

Respuestas:

12

Prueba esto en :

perl -lane 'print $F[0], "\t", "=" x ($F[1] / 5)' file

EXPLICACIONES

  • -aes un explícito split()en la @Fmatriz, obtenemos los valores con$F[n]
  • x es decirle a Perl que imprima un personaje N veces
  • ($F[1] / 5) : aquí obtenemos el número y lo dividimos por 5 para obtener una salida de impresión bonita
Gilles Quenot
fuente
1
perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)'Se ve realmente genial :) gracias
Natim
12

En perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • ehace que se evalúe la expresión, por lo que me =repiten usando el valor de $1(el número que coincide (\d+)).
  • Podría hacerlo en "="x($1\/3)lugar de "="x$1obtener líneas más cortas. (Se /escapa porque estamos en medio de un comando de sustitución).

En bash(inspirado en esta respuesta SO ):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printfrellena la segunda cadena usando espacios para obtener un ancho de $n ( %${n}s), y reemplazo los espacios con =.
  • Las columnas se delimitan utilizando una pestaña ( \t), pero puede hacerla más bonita canalizando a column -ts'\t'.
  • Puede usar en $((n/3))lugar de ${n}obtener líneas más cortas.

Otra version:

unset IFS; printf "%s\t%*s\n" $(sed 's/$/ =/' test.txt) | tr ' ' =

El único inconveniente que puedo ver es que necesitará canalizar sedla salida a algo si desea reducir, de lo contrario, esta es la opción más limpia. Si existe la posibilidad de que su archivo de entrada contenga uno de [?*ustedes, debe dirigir el comando w / set -f;.

muru
fuente
2
Bravo por mostrar una solución de shell también. Su solución Perl también es muy limpia.
pollitos
@mikeserv ¡Maravilloso! Siempre olvido %*sa pesar de que fue el primer printftruco relacionado que aprendí en la programación C.
muru
La printf(sed) | trversión no funciona aquí hasta donde puedo decir.
Natim
@Natim aquí, ¿dónde está?
muru
@mikeserv limitaciones en la longitud del argumento, tal vez?
muru
6

Fácil con awk

awk '{$2=sprintf("%-*s", $2, ""); gsub(" ", "=", $2); printf("%-10s%s\n", $1, $2)}' file

2015/1/7 ========
2015/1/8 =================================================
2015/1/9 ========================================
..
..

O con mi lenguaje de programación favorito

python3 -c 'import sys
for line in sys.stdin:
  data, width = line.split()
  print("{:<10}{:=<{width}}".format(data, "", width=width))' <file
iruvar
fuente
3

Qué tal si:

#! /bin/bash
histo="======================================================================+"

read datewd value

while [ -n "$datewd" ] ; do
   # Use a default width of 70 for the histogram
   echo -n "$datewd      "
   echo ${histo:0:$value}

   read datewd value
done

Que produce:

~/bash $./histogram.sh < histdata.txt
2015/1/7    ========
2015/1/8    =================================================
2015/1/9    ========================================
2015/1/10   ======================================================================+
2015/1/11   ===========
2015/1/12   ===
2015/1/13   =========
2015/1/14   ======================================================================+
2015/1/15   ==============================================================
2015/1/16   ==========
2015/1/17   ==============================
2015/1/18   ==============================
2015/1/19   =
2015/1/20   ===
2015/1/21   =======================
2015/1/22   ============
2015/1/24   ======
2015/1/25   ===
2015/1/27   ==
2015/1/28   ================
2015/1/29   =
2015/2/1    ============
2015/2/2    ==
2015/2/3    =
2015/2/4    ==========
2015/2/5    =============
2015/2/6    ==
2015/2/9    ==
2015/2/10   =========================
2015/2/11   =
2015/2/12   ======
2015/2/13   ============
2015/2/14   ==
2015/2/16   ========
2015/2/17   ========
2015/2/20   =
2015/2/23   =
2015/2/27   =
2015/3/2    ===
2015/3/3    ==
~/bash $
Robert Nix
fuente
1

Esto me pareció un divertido problema de línea de comando tradicional. Aquí está mi bashsolución de script:

awk '{if (count[$1]){count[$1] += $2} else {count[$1] = $2}} \
        END{for (year in count) {print year, count[year];}}' data |
sed -e 's/\// /g' | sort -k1,1n -k2,2n -k3,3n |
awk '{printf("%d/%d/%d\t", $1,$2,$3); for (i=0;i<$4;++i) {printf("=")}; printf("\n");}'

La pequeña secuencia de comandos anterior supone que los datos están en un archivo llamado imaginativamente "datos".

No estoy muy contento con la línea "ejecutarlo a través de sed y ordenar": sería innecesario si su mes y día del mes siempre tuvieran 2 dígitos, pero así es la vida.

Además, como nota histórica, los Unix tradicionales solían venir con una utilidad de trazado de línea de comando que podía hacer gráficos y diagramas ASCII bastante feos. No recuerdo el nombre, pero parece que GNU plotutils reemplaza la antigua utilidad tradicional.

Bruce Ediger
fuente
¿No debería ser eso if ($1 in count) ...?
Muru
1
@muru: parece funcionar de cualquier manera. Sin embargo, encontré un error tipográfico en la cláusula "else". Gracias.
Bruce Ediger
1

Buen ejercicio aquí. Volqué los datos en un archivo llamado "datos" porque soy muy imaginativo.

Bueno, lo pediste en bash ... aquí está en puro bash.

cat data | while read date i; do printf "%-10s " $date; for x in $(seq 1 $i); do echo -n "="; done; echo; done

awk es una mejor opción.

awk '{ s=" ";while ($2-->0) s=s"=";printf "%-10s %s\n",$1,s }' data
Falsenames
fuente
¿Puedes canalizar los datos a través de awk en lugar de usar un archivo?
Natim
Sí, es lo mismo de cualquier manera. Simplemente agregue un "datos de gato |" al principio como tenía para los bits bash, o un "<datos" al final. O incluso puede tener la parte awk sin un archivo especificado, pegar los datos y presionar ctrl-D al final. La especificación del archivo solo trata ese archivo como stdin, y no quería seguir copiando y pegando el archivo de datos porque soy vago.
Falsenames
1
En realidad, acabo de releer la pregunta al vincular esto con un compañero de trabajo ... dijiste que tenías "salida", no un archivo de datos. Así que puedes ejecutar lo que sea que esté creando ese informe, luego canalizarlo a awk, y listo. Las canalizaciones solo dirigen la salida del último comando como la fuente de entrada para el siguiente comando.
Falsenames
0

Prueba esto:

while read value count; do
    printf '%s:\t%s\n' "${value}" "$(printf "%${count}s" | tr ' ' '=')"
done <path/to/my-output

La única parte difícil es la construcción de la barra. Lo hago aquí delegando printfy me trgusta esta respuesta SO .

Como beneficio adicional, es compatible con shPOSIX.

Referencias

rubicks
fuente