Dessiner un histogramme à partir d'une sortie de commande bash

31

J'ai la sortie suivante:

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

Et je voudrais dessiner un histogramme

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

Savez-vous s'il existe une commande bash qui me permettrait de faire cela?

Natim
la source
1
bashplotlib est une bonne solution
Michael Mior
C'est en effet l'un des risques de fournir des liens au lieu de réponses autonomes. Si la réponse SO supprimée est utile, veuillez la publier comme réponse ici.
Jeff Schaller

Réponses:

12

Essayez ceci en :

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

EXPLICATIONS:

  • -aest explicite split()dans @Ftableau, nous obtenons les valeurs avec$F[n]
  • x est de dire à perl d'imprimer un caractère N fois
  • ($F[1] / 5) : ici on obtient le nombre et on le divise par 5 pour une jolie sortie imprimée
Gilles Quenot
la source
1
perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)'Il a l'air vraiment génial :) merci
Natim
12

Dans perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • eprovoque l'expression à évaluer, donc je suis =répété en utilisant la valeur de $1(le nombre correspondant à (\d+)).
  • Vous pourriez faire "="x($1\/3)au lieu d' "="x$1obtenir des lignes plus courtes. (Le /est échappé car nous sommes au milieu d'une commande de substitution.)

Dans bash(inspiré de cette réponse SO ):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printfremplit la deuxième chaîne en utilisant des espaces pour obtenir une largeur de $n ( %${n}s), et je remplace les espaces par =.
  • Les colonnes sont délimitées à l'aide d'un onglet ( \t), mais vous pouvez le rendre plus joli en le redirigeant vers column -ts'\t'.
  • Vous pouvez utiliser $((n/3))au lieu de ${n}pour obtenir des lignes plus courtes.

Une autre version:

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

Le seul inconvénient que je peux voir, c'est que vous aurez besoin de diriger sedla sortie de quelque chose si vous voulez réduire, sinon c'est l'option la plus propre. S'il y a une chance que votre fichier d'entrée contienne l'un de [?*vous, dirigez la commande w / set -f;.

muru
la source
2
Bravo d'avoir montré une solution shell aussi. Votre solution Perl est également très propre.
poussins
@mikeserv Wonderful! J'oublie toujours, %*smême si c'était la première printfastuce liée à la programmation C.
muru
Pour printf(sed) | trautant que je sache, la version ne fonctionne pas ici.
Natim
@Natim ici où?
muru
@mikeserv limitations de la longueur des arguments peut-être?
muru
6

Facile avec awk

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

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

Ou avec mon langage de programmation préféré

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

Que diriez-vous:

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

Ce qui produit:

~/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
la source
1

Cela m'a frappé comme un problème amusant de ligne de commande traditionnelle. Voici ma bashsolution 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");}'

Le petit script ci-dessus suppose que les données se trouvent dans un fichier nommé imaginativement "données".

Je ne suis pas trop content de la ligne "run it through sed and sort" - il ne serait pas nécessaire que votre mois et votre jour de mois aient toujours 2 chiffres, mais c'est la vie.

De plus, comme note historique, les Unix traditionnels étaient livrés avec un utilitaire de traçage en ligne de commande qui pouvait faire des graphiques et des tracés ASCII assez laids. Je ne me souviens pas du nom, mais il semble que les tracés GNU remplacent l'ancien utilitaire traditionnel.

Bruce Ediger
la source
N'est-ce pas if ($1 in count) ...?
muru
1
@muru - semble fonctionner dans les deux cas. Cependant, j'ai trouvé une faute de frappe dans la clause "else". Merci.
Bruce Ediger
1

Bon exercice ici. J'ai vidé les données dans un fichier appelé "données" parce que je suis très imaginatif.

Eh bien, vous l'avez demandé en bash ... ici c'est en bash pur.

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

awk est une meilleure option.

awk '{ s=" ";while ($2-->0) s=s"=";printf "%-10s %s\n",$1,s }' data
Falsenames
la source
Pouvez-vous diriger les données via awk au lieu d'utiliser un fichier?
Natim
Oui, c'est la même chose de toute façon. Ajoutez simplement un "cat data |" au début comme je l'avais pour les bits bash, ou un "<data" à la fin. Ou vous pouvez même simplement avoir la partie awk sans fichier spécifié, coller les données et appuyer sur ctrl-D à la fin. Spécifier le fichier traite simplement ce fichier comme stdin, et je ne voulais pas continuer à copier et coller le fichier de données parce que je suis paresseux.
Falsenames
1
En fait, je viens de relire la question en reliant cela à un collègue ... vous avez dit que vous aviez "une sortie", pas un fichier de données. Vous pouvez donc simplement exécuter ce qui crée ce rapport, puis le diriger vers awk, et vous avez terminé. Les tuyaux dirigent simplement la sortie de la dernière commande comme source d'entrée pour la commande suivante.
Falsenames
0

Essaye ça:

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

La seule partie délicate est la construction de la barre. Je le fais ici en déléguant à printfet trcomme cette réponse SO .

En prime, il est compatible shPOSIX.

Les références:

rubicks
la source