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?

2
  • 4
    bashplotlib is a nice solution Commented Nov 9, 2018 at 15:12
  • That is indeed one of the risks of providing links instead of self-contained answers. If the deleted SO answer is useful, please post it as an answer here.
    – Jeff Schaller
    Commented May 21, 2019 at 14:59

9 Answers 9

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;.

12
  • 3
    Bravo for showing a shell solution too. Your Perl solution is very clean as well.
    – chicks
    Commented Jan 6, 2015 at 17:38
  • @mikeserv Wonderful! I always forget %*s even though it was the first printf-related trick I learnt in C programming.
    – muru
    Commented Jan 7, 2015 at 2:22
  • The printf(sed) | tr version doesn't works here as far as I can tell.
    – Natim
    Commented Jan 7, 2015 at 9:03
  • @Natim here being where?
    – muru
    Commented Jan 7, 2015 at 10:42
  • @mikeserv limitations in argument length perhaps?
    – muru
    Commented Jan 7, 2015 at 11:55
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
  • 1
    perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)' It looks really great :) thanks
    – Natim
    Commented Jan 7, 2015 at 9: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
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 $
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   *.......................................
.
.
.
4
  • Miller is new to me. It is very cool!
    – JJoao
    Commented Sep 19, 2020 at 9:34
  • 2
    Could you please explain a little more what does each parameter do? Even reading the reference is not clear to me
    – Pablo A
    Commented Sep 24, 2020 at 1:48
  • Didn't know about Miller until today. Thank you!
    – dimitarvp
    Commented Jan 30, 2021 at 20:57
  • Date formatting of single digits should have leading zero.
    – NeilG
    Commented May 1 at 5:43
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

6
  • 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
    Commented Aug 18, 2021 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
    Commented Aug 18, 2021 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 golangdocs.com/plotting-in-golang-histogram-barplot-boxplot But i definitely going to try your command.
    – mh-cbon
    Commented Aug 18, 2021 at 18:06
  • there is something! But the scaling is bad. let me write a question ^^
    – mh-cbon
    Commented Aug 18, 2021 at 18:12
  • unix.stackexchange.com/questions/665243/… if you want to take a look
    – mh-cbon
    Commented Aug 18, 2021 at 18:17
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
  • Shouldn't that be if ($1 in count) ...?
    – muru
    Commented Jan 6, 2015 at 18:58
  • 1
    @muru - seems to work either way. However, I did find a typo in the "else" clause. Thanks.
    – user732
    Commented Jan 6, 2015 at 19:19
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
3
  • Can you pipe the data through awk instead of using a file?
    – Natim
    Commented Jan 7, 2015 at 8: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
    Commented Jan 7, 2015 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
    Commented Jan 7, 2015 at 17:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .