Rysowanie histogramu z danych wyjściowych polecenia bash


31

Mam następujące dane wyjściowe:

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

I chciałbym narysować histogram

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

Czy wiesz, czy istnieje polecenie bash, które pozwoli mi to zrobić?


1
bashplotlib to fajne rozwiązanie
Michael Mior,

Jest to rzeczywiście jedno z zagrożeń związanych z udostępnianiem linków zamiast samodzielnych odpowiedzi. Jeśli usunięta odpowiedź SO jest przydatna, opublikuj ją jako odpowiedź tutaj.
Jeff Schaller

Odpowiedzi:


12

Spróbuj tego w :

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

OBJAŚNIENIA:

  • -ajest jawne split()w @Ftablicy, otrzymujemy wartości z$F[n]
  • x mówi perlowi, żeby wypisał znak N razy
  • ($F[1] / 5) : tutaj otrzymujemy liczbę i dzielimy ją przez 5, aby uzyskać ładny wydruk

1
perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)'Wygląda naprawdę świetnie :) dzięki
Natim

12

W perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • epowoduje, że wyrażenie jest oceniane, więc =powtarzam się przy użyciu wartości $1(liczba dopasowana przez (\d+)).
  • Możesz zrobić "="x($1\/3)zamiast "="x$1krótszych linii. (Znak /ucieczki, ponieważ jesteśmy w trakcie wykonywania polecenia zmiany).

W bash(inspirowane tą odpowiedzią SO ):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printfwstawia drugi ciąg za pomocą spacji, aby uzyskać szerokość $n ( %${n}s), i zastępuję spacje =.
  • Kolumny są rozdzielane za pomocą tab ( \t), ale możesz uczynić je ładniejszym przez potokowanie do column -ts'\t'.
  • Możesz użyć $((n/3))zamiast, ${n}aby uzyskać krótsze linie.

Inna wersja:

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

Jedyną wadą, jaką widzę, jest to, że musisz przesłać seddane wyjściowe potoku do czegoś, jeśli chcesz zmniejszyć skalę, w przeciwnym razie jest to najczystsza opcja. Jeśli istnieje szansa, że ​​plik wejściowy zawierający jednego z [?*was powinien przewodzić polecenie w / set -f;.


2
Brawo za pokazanie rozwiązania powłoki. Twoje rozwiązanie w Perlu jest również bardzo czyste.
pisklęta

@mikeserv Wonderful! Zawsze zapominam, %*smimo że była to pierwsza printfsztuczka, której nauczyłem się w programowaniu C.
muru

printf(sed) | trWersja nie działa tu o ile mogę powiedzieć.
Natim,

@Natim tu jest gdzie?
muru

@mikeserv ograniczenia długości argumentów?
muru

6

Łatwo z awk

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

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

Lub z moim ulubionym językiem programowania

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

3

Co powiesz na:

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

Który produkuje:

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

Uderzyło mnie to w tradycyjny, zabawny problem z wierszem poleceń. Oto moje bashrozwiązanie skryptu:

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

W powyższym małym skrypcie założono, że dane znajdują się w pliku wyobraźni o nazwie „dane”.

Nie jestem zbyt zadowolony z linii „przeprowadź przez sed i sortuj” - nie byłoby niepotrzebne, gdyby Twój miesiąc i dzień miesiąca zawsze zawierały 2 cyfry, ale takie jest życie.

Co więcej, jako historyczna notatka, tradycyjne Uniksy były dostarczane z narzędziem do kreślenia linii poleceń, które potrafiło robić dość brzydkie wykresy i wykresy ASCII. Nie pamiętam nazwy, ale wygląda na to, że plotutils GNU zastępują stare tradycyjne narzędzie.


Nie powinno tak być if ($1 in count) ...?
muru

1
@muru - wydaje się działać w obie strony. Znalazłem jednak literówkę w klauzuli „else”. Dzięki.
Bruce Ediger,

1

Ładne ćwiczenie tutaj. Zrzuciłem dane do pliku o nazwie „dane”, ponieważ jestem bardzo pomysłowy.

Cóż, prosiłeś o to w uderzeniu ... tutaj jest w czystym uderzeniu.

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

awk jest lepszą opcją.

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

Czy możesz przesyłać dane przez awk zamiast korzystać z pliku?
Natim

Tak, tak czy inaczej. Wystarczy dodać „dane kota |” na początku, tak jak w przypadku bitów bash, lub „<data” na końcu. Lub możesz nawet mieć część awk bez określonego pliku, wkleić dane i nacisnąć ctrl-D na końcu. Określenie pliku traktuje ten plik jako standardowy, a ja nie chciałem kopiować i wklejać pliku danych, ponieważ jestem leniwy.
Falsenames,

1
Właściwie po prostu ponownie przeczytałem pytanie, łącząc to ze współpracownikiem ... powiedziałeś, że masz „wynik”, a nie plik danych. Możesz więc uruchomić wszystko, co tworzy ten raport, a następnie przesłać go do awk i gotowe. Potokuje bezpośrednie wyjście ostatniego polecenia jako źródło danych wejściowych dla następnego polecenia.
Falsenames,

0

Spróbuj tego:

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

Jedyną trudną częścią jest konstrukcja paska. Robię to tutaj, delegując printfi trpodoba mi się ta SO odpowiedź .

Jako bonus, jest shzgodny z POSIX .

Referencje:

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.