39

I have the following 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

And I'd like to draw a histogram

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

Do you know if there is a bash command that would let me do that?

Natim
  • 537

9 Answers9

18

In perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • e causes the expression to be evaluated, so I get = repeated using the value of $1 (the number matched by (\d+)).
  • You could do "="x($1\/3) instead of "="x$1 to get shorter lines. (The / is escaped since we're in the middle of a substitution command.)

In bash (inspired from this SO answer):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printf pads the second string using spaces to get a width of $n (%${n}s), and I replace the spaces with =.
  • The columns are delimited using a tab (\t), but you can make it prettier by piping to column -ts'\t'.
  • You could use $((n/3)) instead of ${n} to get shorter lines.

Another version:

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

The only drawback I can see is that you'll need to pipe sed's output to something if you want to scale down, otherwise this is the cleanest option. If there is a chance of your input file containing one of [?* you should lead the command w/ set -f;.

muru
  • 72,889
14

Try this in :

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

EXPLANATIONS:

  • -a is an explicit split() in @F array, we get the values with $F[n]
  • x is to tell perl to print a character N times
  • ($F[1] / 5) : here we get the number and divide it by 5 for a pretty print output (simple arithmetic)
  • 1
    perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)' It looks really great :) thanks – Natim Jan 07 '15 at 09:05
11

Easy with awk

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

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

Or with my favourite programming language

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

How about:

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

Which produces:

~/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
  • 91
  • 2
9

You could do something like that with the bar verb in Miller

$ mlr --nidx --repifs --ofs tab bar -f 2 file
2015/1/7    ***.....................................
2015/1/8    *******************.....................
2015/1/9    ****************........................
2015/1/10   ***************************************#
2015/1/11   ****....................................
2015/1/12   *.......................................
.
.
.
steeldriver
  • 81,074
8

(this is not exactly what you ask, but) With Gnuplot, if you are in X, try:

gnuplot -p -e 'set sty d hist;set xtic rot; plot "file" u 2:xtic(1)'

enter image description here

JJoao
  • 12,170
  • 1
  • 23
  • 45
  • this is the answer i was looking for. Thank you ! I would have appreciated to know if it can read stdin. and a quick word about data representation. – mh-cbon Aug 18 '21 at 13:38
  • 2
    @mh-cbon, gnuplot can do manyyyy diferent things (see their docs and demos). Example with stdin seq 30 | gnuplot -p -e 'set style d hist; plot "-" u ($1**3). You can also use `"/dev/stdin"' – JJoao Aug 18 '21 at 17:22
  • oh thank you ! I am pretty aware it is very powerful, but i admit i have not yet found whatsneeded to learn it. A good resource, a topic to work with, motivation. For now, i have resorted to using a plotter written written with the Go language https://golangdocs.com/plotting-in-golang-histogram-barplot-boxplot But i definitely going to try your command. – mh-cbon Aug 18 '21 at 18:06
  • there is something! But the scaling is bad. let me write a question ^^ – mh-cbon Aug 18 '21 at 18:12
  • https://unix.stackexchange.com/questions/665243/improve-plot-scale-and-display if you want to take a look – mh-cbon Aug 18 '21 at 18:17
  • Related: https://stackoverflow.com/questions/2471884/histogram-using-gnuplot – Ciro Santilli OurBigBook.com Jul 22 '23 at 18:23
2

This struck me as a fun traditional command line problem. Here's my bash script solution:

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

The little script above assumes the data is in a file imaginatively named "data".

I'm not too happy with the "run it through sed and sort" line - it would be unnecessary if your month and day-of-month always had 2 digits, but that's life.

Also, as a historical note, traditional Unixes used to come with a command line plotting utility that could do fairly ugly ASCII graphs and plots. I can't remember the name, but it looks like GNU plotutils replace the old traditional utility.

2

Try this:

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

The only tricky part is the construction of the bar. I do it here by delegating to printf and tr like this SO answer.

As a bonus, it's POSIX-sh-compliant.

References:

1

Nice exercise here. I dumped the data in a file called "data" because I am very imaginative.

Well, you asked for it in bash... here it is in pure bash.

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

awk is a better option.

awk '{ s=" ";while ($2-->0) s=s"=";printf "%-10s %s\n",$1,s }' data
  • Can you pipe the data through awk instead of using a file? – Natim Jan 07 '15 at 08:52
  • Yes, it's the same thing either way. Just add a "cat data |" at the beginning like I had for the bash bits, or a "<data" at the end. Or you can even just have the awk part without a file specified, paste in the data and hit ctrl-D at the end. Specifying the file just treats that file as stdin, and I didn't want to keep copying and pasting the datafile because I'm lazy. – Falsenames Jan 07 '15 at 16:47
  • 1
    Actually, I just reread the question while linking this to a coworker... you said you had "output", not a data file. So you can just run whatever is creating that report, then pipe it to awk, and you're done. Pipes just direct output of the last command as the source of input for the next command. – Falsenames Jan 07 '15 at 17:06