Traccia un istogramma dall'output del comando bash


31

Ho il seguente output:

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

E vorrei disegnare un istogramma

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

Sai se esiste un comando bash che mi permette di farlo?


1
bashplotlib è una buona soluzione
Michael Mior,

Questo è davvero uno dei rischi di fornire collegamenti invece di risposte autonome. Se la risposta SO eliminata è utile, pubblicarla come risposta qui.
Jeff Schaller

Risposte:


12

Prova questo in :

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

Spiegazioni:

  • -aè un esplicito split()in @Farray, si ottengono i valori con$F[n]
  • x è dire a perl di stampare un carattere N volte
  • ($F[1] / 5) : qui otteniamo il numero e lo dividiamo per 5 per un risultato di stampa carino

1
perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)'Sembra davvero fantastico :) grazie
Natim

12

In perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • efa sì che l'espressione venga valutata, quindi mi viene =ripetuto usando il valore di $1(il numero corrispondente a (\d+)).
  • Si potrebbe fare "="x($1\/3)invece di "="x$1ottenere linee più brevi. ( /È scappato poiché siamo nel mezzo di un comando di sostituzione.)

In bash(ispirato a questa risposta SO ):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printfriempie la seconda stringa usando gli spazi per ottenere una larghezza di $n ( %${n}s) e sostituisco gli spazi con =.
  • Le colonne sono delimitate utilizzando una scheda ( \t), ma puoi renderla più carina eseguendo il piping a column -ts'\t'.
  • È possibile utilizzare $((n/3))invece di ${n}ottenere linee più brevi.

Un'altra versione:

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

L'unico inconveniente che posso vedere è che dovrai sedridimensionare l'output su qualcosa se vuoi ridimensionare, altrimenti questa è l'opzione più pulita. Se esiste la possibilità che il tuo file di input contenga uno di [?*voi, dovresti condurre il comando w / set -f;.


2
Bravo per aver mostrato anche una soluzione shell. Anche la tua soluzione Perl è molto pulita.
pulcini,

@mikeserv Wonderful! Dimentico sempre %*sanche se è stato il primo printftrucco correlato che ho imparato nella programmazione in C.
Muru,

La printf(sed) | trversione non funziona qui per quanto posso dire.
Natim

@Natim qui essendo dove?
Muru,

Limitazioni di @mikeserv nella lunghezza dell'argomento forse?
Muru,

6

Facile 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 il mio linguaggio di programmazione preferito

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

3

Che ne dite di:

#! /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

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

1

Questo mi ha colpito come un divertente tradizionale problema da riga di comando. Ecco la mia bashsoluzione di 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");}'

Il piccolo script sopra presuppone che i dati si trovino in un file immaginariamente chiamato "dati".

Non sono molto contento della linea "esegui sed e ordina" - non sarebbe necessario se il tuo mese e il tuo giorno del mese avessero sempre 2 cifre, ma quella è vita.

Inoltre, come nota storica, gli Unix tradizionali erano dotati di un'utilità di tracciamento della riga di comando che poteva eseguire grafici e diagrammi ASCII abbastanza brutti. Non ricordo il nome, ma sembra che i plotutils GNU sostituiscano la vecchia utility tradizionale.


Non dovrebbe essere if ($1 in count) ...?
Muru,

1
@muru - sembra funzionare in entrambi i modi. Tuttavia, ho trovato un refuso nella clausola "else". Grazie.
Bruce Ediger,

1

Bel esercizio qui. Ho scaricato i dati in un file chiamato "data" perché sono molto fantasioso.

Bene, l'hai chiesto a bash ... eccolo in 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 è un'opzione migliore.

awk '{ s=" ";while ($2-->0) s=s"=";printf "%-10s %s\n",$1,s }' data

Puoi reindirizzare i dati tramite awk invece di utilizzare un file?
Natim

Sì, è la stessa cosa in entrambi i casi. Basta aggiungere un "cat data |" all'inizio come avevo fatto per i bit bash o un "<dato" alla fine. Oppure puoi anche solo avere la parte awk senza un file specificato, incollare i dati e premere ctrl-D alla fine. Specificare il file lo tratta solo come stdin e non volevo continuare a copiare e incollare il file di dati perché sono pigro.
Falsenames,

1
In realtà, ho appena riletto la domanda mentre lo collegavo a un collega ... hai detto che avevi "output", non un file di dati. Quindi puoi semplicemente eseguire tutto ciò che sta creando quel rapporto, quindi reindirizzarlo su awk e il gioco è fatto. Pipa semplicemente l'output diretto dell'ultimo comando come sorgente di input per il comando successivo.
Falsenames,

0

Prova questo:

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

L'unica parte difficile è la costruzione del bar. Lo faccio qui delegando printfe trcome questa risposta SO .

Come bonus, è conforme a POSIX sh.

Riferimenti:

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.