Zeichnen eines Histogramms aus einer Ausgabe eines Bash-Befehls

31

Ich habe folgende Ausgabe:

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

Und ich möchte ein Histogramm zeichnen

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

Wissen Sie, ob es einen Bash-Befehl gibt, mit dem ich das tun kann?

Natim
quelle
1
bashplotlib ist eine schöne Lösung
Michael Mior
Dies ist in der Tat eines der Risiken, Links anstelle von in sich geschlossenen Antworten bereitzustellen. Wenn die gelöschte SO-Antwort nützlich ist, posten Sie sie bitte als Antwort hier.
Jeff Schaller

Antworten:

12

Versuchen Sie dies in :

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

ERLÄUTERUNGEN:

  • -aein explizites werde split()in @FArray, erhalten wir die Werte mit$F[n]
  • x ist, Perl anzuweisen, ein Zeichen N-mal zu drucken
  • ($F[1] / 5) : Hier bekommen wir die Zahl und teilen sie durch 5 für eine schöne Druckausgabe
Gilles Quenot
quelle
1
perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)'Es sieht wirklich toll aus :) Danke
Natim
12

In perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • eBewirkt, dass der Ausdruck ausgewertet wird, sodass ich ihn =mit dem Wert von $1(der Zahl, die mit übereinstimmt (\d+)) wiederhole .
  • Sie könnten tun, "="x($1\/3)anstatt "="x$1kürzere Zeilen zu erhalten. (Das /wird ausgeblendet, da wir uns mitten in einem Ersetzungsbefehl befinden.)

In bash(inspiriert von dieser SO-Antwort ):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printfFüllt die zweite Zeichenkette mit Leerzeichen auf, um eine Breite von $n ( %${n}s) zu erhalten, und ich ersetze die Leerzeichen durch =.
  • Die Spalten werden mit einem Tabulator ( \t) abgegrenzt , Sie können sie jedoch schöner gestalten, indem Sie eine Pipeline an senden column -ts'\t'.
  • Sie können $((n/3))stattdessen verwenden ${n}, um kürzere Zeilen zu erhalten.

Andere Version:

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

Der einzige Nachteil, den ich sehen kann, ist, dass Sie die sedAusgabe an etwas weiterleiten müssen, wenn Sie die Größe verringern möchten. Andernfalls ist dies die sauberste Option. Wenn es eine Chance gibt, dass Ihre Eingabedatei eine von [?*Ihnen enthält, sollten Sie den Befehl w / ausführen set -f;.

muru
quelle
2
Bravo für das Zeigen einer Schalenlösung auch. Ihre Perl-Lösung ist ebenfalls sehr sauber.
Küken
@mikeserv Wundervoll! Ich vergesse immer, %*sobwohl es der erste printfTrick war, den ich in der C-Programmierung gelernt habe.
muru
printf(sed) | trSoweit ich das beurteilen kann, funktioniert die Version hier nicht.
Natim
@Natim hier wo zu sein?
muru
@mikeserv Einschränkungen in der Argumentlänge vielleicht?
muru
6

Einfach mit awk

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

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

Oder mit meiner Lieblingsprogrammiersprache

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

Wie wäre es mit:

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

Welches produziert:

~/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
quelle
1

Dies erschien mir als lustiges traditionelles Kommandozeilenproblem. Hier ist meine bashSkriptlösung:

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

Das kleine obige Skript geht davon aus, dass sich die Daten in einer Datei befinden, die einfallsreich als "Daten" bezeichnet wird.

Ich bin nicht sehr zufrieden mit der Zeile "Durchsuchen und sortieren" - es wäre unnötig, wenn Ihr Monat und Tag des Monats immer zwei Ziffern hätten, aber das ist das Leben.

Außerdem enthielten traditionelle Unixe ein Dienstprogramm zum Zeichnen über die Befehlszeile, mit dem ziemlich hässliche ASCII-Diagramme und -Diagramme erstellt werden konnten. Ich kann mich nicht an den Namen erinnern, aber es sieht so aus, als ob GNU-Plotutils das alte traditionelle Dienstprogramm ersetzen.

Bruce Ediger
quelle
Sollte das nicht sein if ($1 in count) ...?
muru
1
@muru - scheint so oder so zu funktionieren. Ich habe jedoch einen Tippfehler in der "else" -Klausel gefunden. Vielen Dank.
Bruce Ediger
1

Gute Übung hier. Ich habe die Daten in einer Datei namens "data" abgelegt, weil ich sehr einfallsreich bin.

Nun, Sie haben in bash danach gefragt ... hier ist es in purer bash.

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

awk ist eine bessere Option.

awk '{ s=" ";while ($2-->0) s=s"=";printf "%-10s %s\n",$1,s }' data
Falsche Namen
quelle
Können Sie die Daten durch awk leiten, anstatt eine Datei zu verwenden?
Natim
Ja, so oder so ist es dasselbe. Fügen Sie einfach "Katzendaten |" hinzu. am anfang wie ich für die bash bits hatte, oder ein "<data" am ende. Oder Sie können den awk-Teil auch nur ohne Angabe einer Datei haben, die Daten einfügen und am Ende Strg-D drücken. Wenn Sie die Datei angeben, wird diese Datei nur als stdin behandelt, und ich wollte die Datendatei nicht weiter kopieren und einfügen, weil ich faul bin.
Falseames
1
Eigentlich habe ich die Frage nur noch einmal gelesen, während ich sie mit einem Kollegen verknüpfte ... Sie sagten, Sie hätten "Ausgabe", keine Datendatei. Sie können also einfach ausführen, was auch immer diesen Bericht erstellt, ihn dann an awk weiterleiten und fertig. Leitet die direkte Ausgabe des letzten Befehls als Eingabequelle für den nächsten Befehl weiter.
Falseames
0

Versuche dies:

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

Der einzige schwierige Teil ist der Bau der Bar. Ich mache es hier, indem ich an diese SO-Antwort delegiere printfund sie trmag .

Als Bonus ist es POSIX-konform sh.

Verweise:

rubicks
quelle