report variables unformatted on trailing #
[barcat.git] / barcat
1 #!/usr/bin/perl -CA
2 use 5.014;
3 use warnings;
4 use utf8;
5 use List::Util qw( min max sum );
6 use open qw( :std :utf8 );
7 use re '/msx';
8
9 our $VERSION = '1.09';
10
11 my %opt;
12 if (@ARGV) {
13 require Getopt::Long;
14 Getopt::Long->import('2.33', qw( :config gnu_getopt ));
15 GetOptions(\%opt,
16         'ascii|a!',
17         'color|C!',
18         'M' => sub { $opt{color} = 0 },
19         'field|f=s' => sub {
20                 eval {
21                         local $_ = $_[1];
22                         s/\A[0-9]+\z/(?:\\S*\\h+){$_}\\K/;
23                         s{\A[+]([0-9]*)\z}{
24                                 (!!$1 && '(?:\d+\D+\b){'.$1.'}\K') . '\s* (?=\d)'
25                         }e;
26                         $opt{anchor} = qr/$_/;
27                 } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r;
28         },
29         'human-readable|H!',
30         'sexagesimal!',
31         'reformat!',
32         'interval|t:i',
33         'trim|length|l=s' => sub {
34                 my ($optname, $optval) = @_;
35                 $optval =~ s/%$// and $opt{trimpct}++;
36                 $optval =~ m/\A-?[0-9]+\z/ or die(
37                         "Value \"$optval\" invalid for option $optname",
38                         " (number or percentage expected)\n"
39                 );
40                 $opt{trim} = $optval;
41         },
42         'value-length=i',
43         'hidemin=i',
44         'hidemax=i',
45         'minval=f',
46         'maxval=f',
47         'limit|L:s' => sub {
48                 my ($optname, $optval) = @_;
49                 $optval ||= 0;
50                 $optval =~ /\A-[0-9]+\z/ and $optval .= '-';  # tail shorthand
51                 ($opt{hidemin}, $opt{hidemax}) =
52                 $optval =~ m/\A (?: (-? [0-9]+)? - )? ([0-9]+)? \z/ or die(
53                         "Value \"$optval\" invalid for option limit",
54                         " (range expected)\n"
55                 );
56         },
57         'log|e!',
58         'header!',
59         'markers|m=s',
60         'graph-format=s' => sub {
61                 $opt{'graph-format'} = substr $_[1], 0, 1;
62         },
63         'spark|_!',
64         'indicators:s',
65         'palette=s' => sub {
66                 $opt{palette} = {
67                         ''     => [],
68                         fire   => [qw( 90 31 91 33 93 97 96 )],
69                         fire256=> [map {"38;5;$_"} qw(
70                                 235  52 88 124 160 196
71                                 202 208 214 220 226  227 228 229 230 231  159
72                         )],
73                         whites => [qw( 1;30 0;37 1;37 )],
74                         greys  => [map {"38;5;$_"} 0, 232..255, 15],
75                         random => [map {"38;5;$_"} List::Util::shuffle(17..231)],
76                         rainbow=> [map {"38;5;$_"}
77                                 196, # r
78                                 (map { 196 + $_*6   } 0..4), # +g
79                                 (map { 226 - $_*6*6 } 0..4), # -r
80                                 (map {  46 + $_     } 0..4), # +b
81                                 (map {  51 - $_*6   } 0..4), # -g
82                                 (map {  21 + $_*6*6 } 0..4), # +r
83                                 (map { 201 - $_     } 0..4), # -b
84                                 196,
85                         ],
86                 }->{$_[1]} // do {
87                         my @vals = split /[^0-9;]/, $_[1]
88                                 or die "Empty palette resulting from \"$_[1]\"\n";
89                         \@vals;
90                 };
91         },
92         'stat|s!',
93         'report=s',
94         'signal-stat=s',
95         'unmodified|u!',
96         'width|w=i',
97         'version|V' => sub {
98                 my $mascot = $opt{ascii} ? '=^,^=' : 'ฅ^•ﻌ•^ฅ';
99                 say "barcat $mascot version $VERSION";
100                 exit;
101         },
102         'usage|h' => sub {
103                 /^=/ ? last : print for readline *DATA;  # text between __END__ and pod
104                 exit;
105         },
106         'help|?'  => sub {
107                 require Pod::Usage;
108                 Pod::Usage::pod2usage(
109                         -exitval => 0, -perldocopt => '-oman', -verbose => 2,
110                 );
111         },
112 ) or exit 64;  # EX_USAGE
113 }
114
115 $opt{width} ||= $ENV{COLUMNS} || qx(tput cols) || 80 unless $opt{spark};
116 $opt{color} //= $ENV{NO_COLOR} ? 0 : -t *STDOUT;  # enable on tty
117 $opt{'graph-format'} //= '-';
118 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
119 $opt{units}   = [split //, ' kMGTPEZYRQqryzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
120         if $opt{'human-readable'};
121 $opt{anchor} //= qr/\A/;
122 $opt{'value-length'} = 4 if $opt{units};
123 $opt{'value-length'} = 1 if $opt{unmodified};
124 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
125 $opt{markers} //= '=avg >31.73v <68.27v +50v |0';
126 $opt{report} //= join(', ',
127         '${min; color(31)} min',
128         '${avg; $opt{reformat} or $_ = sprintf "%0.2f", $_; color(36)} avg',
129         '${max; color(32)} max',
130 );
131 $opt{palette} //= $opt{color} && [31, 90, 32];
132 $opt{indicators} = [split //, $opt{indicators} ||
133         ($opt{ascii} ? ' .oO' : $opt{spark} ? ' ▁▂▃▄▅▆▇█' : ' ▏▎▍▌▋▊▉█')
134 ] if defined $opt{indicators} or $opt{spark};
135 $opt{hidemin} = ($opt{hidemin} || 1) - 1;
136 $opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
137         and undef $opt{interval};
138
139 $opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] };
140 $opt{'value-format'} = $opt{sexagesimal} ? sub {
141         my $s = abs($_[0]) + .5;
142         sprintf('%s%d:%02d:%02d', $_[0] < 0 && '-', $s/3600, $s/60%60, $s%60);
143 } : $opt{units} && sub {
144         my $unit = (
145                 log(abs $_[0] || 1) / log(10)
146                 - 3 * (abs($_[0]) < .9995)   # shift to smaller unit if below 1
147                 + 1e-15  # float imprecision
148         );
149         my $decimal = ($unit % 3) == ($unit < 0);
150         $unit -= log($decimal ? .995 : .9995) / log(10);  # rounded
151         $decimal = ($unit % 3) == ($unit < 0);
152         $decimal &&= $_[0] !~ /^-?0*[0-9]{1,3}$/;  # integer 0..999
153         sprintf('%*.*f%1s',
154                 3 + ($_[0] < 0), # digits plus optional negative sign
155                 $decimal,  # tenths
156                 $_[0] / 1000 ** int($unit/3),  # number
157                 $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) :
158                         $opt{units}->[$unit/3]  # suffix
159         );
160 } and $opt{reformat}++;
161 $opt{'value-format'} ||= sub { sprintf '%.8g', $_[0] };
162
163
164 my (@lines, @values, @order);
165
166 $SIG{$_} = \&show_stat for $opt{'signal-stat'} || ();
167 $SIG{ALRM} = sub {
168         show_lines();
169         alarm $opt{interval} if defined $opt{interval} and $opt{interval} > 0;
170 };
171 $SIG{INT} = \&show_exit;
172
173 if (defined $opt{interval}) {
174         $opt{interval} ||= 1;
175         alarm $opt{interval} if $opt{interval} > 0;
176
177         eval {
178                 require Tie::Array::Sorted;
179                 tie @order, 'Tie::Array::Sorted', sub { $_[1] <=> $_[0] };
180         } or warn $@, "Expect slowdown with large datasets!\n";
181 }
182
183 my $valmatch = qr<
184         $opt{anchor} ( \h* -? [0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )? |)
185 >x;
186 while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
187         s/\r?\n\z//;
188         s/\A\h*// unless $opt{unmodified};
189         my $valnum = s/$valmatch/\n/ && $1;
190         push @values, $valnum;
191         push @order, $valnum if length $valnum;
192         if (defined $opt{trim} and defined $valnum) {
193                 my $trimpos = abs $opt{trim};
194                 $trimpos -= length $valnum if $opt{unmodified};
195                 if ($trimpos <= 1) {
196                         $_ = substr $_, 0, 2;
197                 }
198                 elsif (length > $trimpos) {
199                         # cut and replace (intentional lvalue for speed, contrary to PBP)
200                         substr($_, $trimpos - 1) = $opt{ascii} ? '>' : '…';
201                 }
202         }
203         push @lines, $_;
204         show_lines() if defined $opt{interval} and $opt{interval} < 0
205                 and $. % $opt{interval} == 0;
206 }
207
208 $SIG{INT} = 'DEFAULT';
209
210 sub color {
211         $opt{color} and defined $_[0] or return '';
212         return "\e[$_[0]m" if defined wantarray;
213         $_ = color(@_) . $_ . color(0) if defined;
214 }
215
216 sub show_lines {
217
218 state $nr =
219         $opt{hidemin} < 0 ? max(0, @lines + $opt{hidemin} + 1) :
220         $opt{hidemin};
221 @lines > $nr or return;
222
223 my $limit = $#lines;
224 if (defined $opt{hidemax}) {
225         if ($opt{hidemin} and $opt{hidemin} < 0) {
226                 $limit -= $opt{hidemax} - 1;
227         }
228         elsif ($opt{hidemax} <= $limit) {
229                 $limit = $opt{hidemax} - 1;
230         }
231 }
232
233 @order = sort { $b <=> $a } @order unless tied @order;
234 my $maxval = $opt{maxval} // (
235         $opt{hidemax} ? max grep { length } @values[$nr .. $limit] :
236         $order[0]
237 ) // 0;
238 my $minval = $opt{minval} // min $order[-1] // (), 0;
239 my $range = $maxval - $minval;
240 $range &&= log $range if $opt{log};
241 my $lenval = $opt{'value-length'} // max map { length } @order;
242 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
243         max map { length $values[$_] && length $lines[$_] }
244                 0 .. min $#lines, $opt{hidemax} || ();  # left padding
245 my $size   = defined $opt{width} && $range &&
246         ($opt{width} - $lenval - $len - !!$opt{indicators});  # bar multiplication
247
248 my @barmark;
249 if ($opt{markers} and $size > 0) {
250         for my $markspec (split /\h/, $opt{markers}) {
251                 my ($char, $func) = split //, $markspec, 2;
252                 my $pos = eval {
253                         if ($func eq 'avg') {
254                                 return sum(@order) / @order;
255                         }
256                         elsif ($func =~ /\A([0-9.]+)v\z/) {
257                                 $1 <= 100 or die(
258                                         "Invalid marker $char: percentile $1 out of bounds\n"
259                                 );
260                                 my $index = $#order * $1 / 100;
261                                 return ($order[$index] + $order[$index + .5]) / 2;
262                         }
263                         elsif ($func =~ /\A-?[0-9.]+\z/) {
264                                 return $func;
265                         }
266                         else {
267                                 die "Unknown marker $char: $func\n";
268                         }
269                 };
270                 defined $pos or do {
271                         warn $@ if $@;
272                         next;
273                 };
274                 $pos -= $minval;
275                 $pos &&= log $pos if $opt{log};
276                 $pos >= 0 or next;
277                 color(36) for $barmark[$pos / $range * $size] = $char;
278         }
279
280         state $lastmax = $maxval;
281         if ($maxval > $lastmax) {
282                 print ' ' x ($lenval + $len);
283                 printf color(90);
284                 printf '%-*s',
285                         ($lastmax - $minval) * $size / $range + .5,
286                         '-' x (($values[$nr - 1] - $minval) * $size / $range);
287                 print color(92);
288                 say '+' x (($range - $lastmax) * $size / $range + .5);
289                 print color(0);
290                 $lastmax = $maxval;
291         }
292 }
293
294 say(
295         color(31), sprintf('%*s', $lenval, $minval),
296         color(90), '-', color(36), '+',
297         color(32), sprintf('%*s', $size - 3, $maxval),
298         color(90), '-', color(36), '+',
299         color(0),
300 ) if $opt{header};
301
302 while ($nr <= $limit) {
303         my $val = $values[$nr];
304         my $rel;
305         if (length $val) {
306                 $rel = $val - $minval;
307                 $rel &&= log $rel if $opt{log};
308                 $rel = min(1, $rel / $range) if $range; # 0..1
309         }
310         my $color = !length $val || !$opt{palette} ? undef :
311                 $val == $order[0] ? $opt{palette}->[-1] : # max
312                 $val == $order[-1] ? $opt{palette}->[0] : # min
313                 $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ];
314         my $indicator = $opt{indicators} && $opt{indicators}->[
315                 !length($val) || !$#{$opt{indicators}} ? 0 : # blank
316                 $#{$opt{indicators}} < 2 ? 1 :
317                 $val >= $order[0] ? -1 :
318                 $rel * ($#{$opt{indicators}} - 1e-14) + 1
319         ];
320
321         if ($opt{spark}) {
322                 say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
323                 print color($color), $_ for $indicator;
324                 next;
325         }
326         print $indicator if defined $indicator;
327
328         if (length $val) {
329                 $val = sprintf("%*s", $lenval,
330                         $opt{reformat} ? $opt{'value-format'}->($val) : $val
331                 );
332                 color($color) for $val;
333         }
334         my $line = $lines[$nr] =~ s/\n/$val/r;
335         if (not length $val) {
336                 say $line;
337                 next;
338         }
339         printf '%-*s', $len + length($val), $line;
340         if ($rel and $size) {
341                 print $barmark[$_] // $opt{'graph-format'}
342                         for 1 .. $rel * $size + .5;
343         }
344         say '';
345 }
346 continue {
347         $nr++;
348 }
349 say $opt{palette} ? color(0) : '' if $opt{spark};
350
351         return $nr;
352 }
353
354 sub show_stat {
355         if ($opt{hidemin} or $opt{hidemax}) {
356                 my $linemin = $opt{hidemin};
357                 my $linemax = ($opt{hidemax} || @lines) - 1;
358                 if ($linemin < 0) {
359                         $linemin += @lines;
360                         $linemax = @lines - $linemax;
361                 }
362                 printf '%.8g of ', $opt{'value-format'}->(
363                         sum(grep {length} @values[$linemin .. $linemax]) // 0
364                 );
365         }
366         if (@order) {
367                 my $total = sum @order;
368                 my $fmt = '${sum;color(1)} total in ${count#} values';
369                 $fmt .= ' over ${lines#} lines' if @order != @lines;
370                 $fmt .= " ($_)" for $opt{report} || ();
371                 print varfmt($fmt, {
372                         sum => $total,
373                         count => int @order,
374                         lines => int @lines,
375                         min => $order[-1],
376                         max => $order[0],
377                         avg => $total / @order,
378                 });
379         }
380         say '';
381         return 1;
382 }
383
384 sub varfmt {
385         my ($fmt, $vars) = @_;
386         $fmt =~ s[\$\{( (?: [^{}]++ | \{(?1)\} )+ )\}]{
387                 my ($name, $cmd) = split /\s*;/, $1, 2;
388                 my $format = $name !~ s/\h*\#// && $opt{reformat};
389                 local $_ = $vars->{$name};
390                 if (defined) {
391                         $_ = $opt{'value-format'}->($_) if $format;
392                         if ($cmd) {
393                                 eval $cmd;
394                                 warn "Error in \$$name report: $@" if $@;
395                         }
396                         $_;
397                 }
398                 else {
399                         warn "Unknown variable \$$name in report\n";
400                         "\$$name";
401                 }
402         }eg;
403         return $fmt;
404 }
405
406 sub show_exit {
407         show_lines();
408         show_stat() if $opt{stat};
409         exit 130 if @_;  # 0x80+signo
410         exit;
411 }
412
413 show_exit();
414
415 __END__
416 Usage:                                               /\_/\
417   barcat [OPTIONS] [FILES|NUMBERS]                  (=•.•=)
418                                                     (u   u)
419 Options:
420   -a, --[no-]ascii         Restrict user interface to ASCII characters
421   -C, --[no-]color         Force colored output of values and bar markers
422   -f, --field=([+]N|REGEXP)
423                            Compare values after a given number of whitespace
424                            separators
425       --header             Prepend a chart axis with minimum and maximum
426                            values labeled
427   -H, --human-readable     Format values using SI unit prefixes
428       --sexagesimal        Convert seconds to HH:MM:SS time format
429   -t, --interval[=(N|-LINES)]
430                            Output partial progress every given number of
431                            seconds or input lines
432   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
433   -L, --limit[=(N|-LAST|START-[END])]
434                            Stop output after a number of lines
435   -e, --log                Logarithmic (exponential) scale instead of linear
436       --graph-format=CHAR  Glyph to repeat for the graph line
437   -m, --markers=FORMAT     Statistical positions to indicate on bars
438       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
439       --palette=(PRESET|COLORS)
440                            Override colors of parsed numbers
441   -_, --spark              Replace lines by sparklines
442       --indicators[=CHARS] Prefix a unicode character corresponding to each
443                            value
444   -s, --stat               Total statistics after all data
445   -u, --unmodified         Do not reformat values, keeping leading whitespace
446       --value-length=SIZE  Reserved space for numbers
447   -w, --width=COLUMNS      Override the maximum number of columns to use
448   -h, --usage              Overview of available options
449       --help               Full pod documentation
450   -V, --version            Version information
451
452 =encoding utf8
453
454 =head1 NAME
455
456 barcat - concatenate texts with graph to visualize values
457
458 =head1 SYNOPSIS
459
460 B<barcat> [<options>] [<file>... | <numbers>]
461
462 =head1 DESCRIPTION
463
464 Visualizes relative sizes of values read from input
465 (parameters, file(s) or STDIN).
466 Contents are concatenated similar to I<cat>,
467 but numbers are reformatted and a bar graph is appended to each line.
468
469 Don't worry, barcat does not drink and divide.
470 It can has various options for input and output (re)formatting,
471 but remains limited to one-dimensional charts.
472 For more complex graphing needs
473 you'll need a larger animal like I<gnuplot>.
474
475 =head1 OPTIONS
476
477 =over
478
479 =item -a, --[no-]ascii
480
481 Restrict user interface to ASCII characters,
482 replacing default UTF-8 by their closest approximation.
483 Input is always interpreted as UTF-8 and shown as is.
484
485 =item -C, --[no-]color
486
487 Force colored output of values and bar markers.
488 Defaults on if output is a tty,
489 disabled otherwise such as when piped or redirected.
490 Can also be disabled by setting I<-M>
491 or the I<NO_COLOR> environment variable.
492
493 =item -f, --field=([+]<number> | <regexp>)
494
495 Compare values after a given number of whitespace separators,
496 or matching a regular expression.
497
498 Unspecified or I<-f0> means values are at the start of each line.
499 With I<-f1> the second word is taken instead.
500 A string can indicate the starting position of a value
501 (such as I<-f:> if preceded by colons),
502 or capture the numbers itself,
503 for example I<-f'(\d+)'> for the first digits anywhere.
504 A shorthand for this is I<+0>, or I<+N> to find the Nth number.
505
506 =item --header
507
508 Prepend a chart axis with minimum and maximum values labeled.
509
510 =item -H, --human-readable
511
512 Format values using SI unit prefixes,
513 turning long numbers like I<12356789> into I<12.4M>.
514 Also changes an exponent I<1.602176634e-19> to I<160.2z>.
515 Short integers are aligned but kept without decimal point.
516
517 =item --sexagesimal
518
519 Convert seconds to HH:MM:SS time format.
520
521 =item -t, --interval[=(<seconds> | -<lines>)]
522
523 Output partial progress every given number of seconds or input lines.
524 An update can also be forced by sending a I<SIGALRM> alarm signal.
525
526 =item -l, --length=[-]<size>[%]
527
528 Trim line contents (between number and bars)
529 to a maximum number of characters.
530 The exceeding part is replaced by an abbreviation sign,
531 unless C<--length=0>.
532
533 Prepend a dash (i.e. make negative) to enforce padding
534 regardless of encountered contents.
535
536 =item -L, --limit[=(<count> | -<last> | <start>-[<end>])]
537
538 Stop output after a number of lines.
539 A single value indicates the last line number (like C<head>),
540 or first line counting from the bottom if negative (like C<tail>).
541 A specific range can be given by two values.
542
543 All input is still counted and analyzed for statistics,
544 but disregarded for padding and bar size.
545
546 =item -e, --log
547
548 Logarithmic (I<e>xponential) scale instead of linear
549 to compare orders of magnitude.
550
551 =item --graph-format=<character>
552
553 Glyph to repeat for the graph line.
554 Defaults to a dash C<->.
555
556 =item -m, --markers=<format>
557
558 Statistical positions to indicate on bars.
559 A single indicator glyph precedes each position:
560
561 =over 2
562
563 =item <number>
564
565 Exact value to match on the axis.
566 A vertical bar at the zero crossing is displayed by I<|0>
567 for negative values.
568 For example I<:3.14> would show a colon at pi.
569
570 =item <percentage>I<v>
571
572 Ranked value at the given percentile.
573 The default shows I<+> at I<50v> for the mean or median;
574 the middle value or average between middle values.
575 One standard deviation right of the mean is at about I<68.3v>.
576 The default includes I<< >31.73v <68.27v >>
577 to encompass all I<normal> results, or 68% of all entries, by B<< <--> >>.
578
579 =item I<avg>
580
581 Matches the average;
582 the sum of all values divided by the number of counted lines.
583 Indicated by default as I<=>.
584
585 =back
586
587 =item --min=<number>, --max=<number>
588
589 Bars extend from 0 or the minimum value if lower,
590 to the largest value encountered.
591 These options can be set to customize this range.
592
593 =item --palette=(<preset> | <color>...)
594
595 Override colors of parsed numbers.
596 Can be any CSI escape, such as I<90> for default dark grey,
597 or alternatively I<1;30> for bright black.
598
599 In case of additional colors,
600 the last is used for values equal to the maximum, the first for minima.
601 If unspecified, these are green and red respectively (I<31 90 32>).
602 Multiple intermediate colors will be distributed
603 relative to the size of values.
604
605 Predefined color schemes are named I<whites> and I<fire>,
606 or I<greys> and I<fire256> for 256-color variants.
607
608 =item -_, --spark
609
610 Replace lines by I<sparklines>,
611 single characters (configured by C<--indicators>)
612 corresponding to input values.
613
614 =item --indicators[=<characters>]
615
616 Prefix a unicode character corresponding to each value.
617 The first specified character will be used for non-values,
618 the remaining sequence will be distributed over the range of values.
619 Unspecified, block fill glyphs U+2581-2588 will be used.
620
621 =item -s, --stat
622
623 Total statistics after all data.
624
625 =item -u, --unmodified
626
627 Do not reformat values, keeping leading whitespace.
628 Keep original value alignment, which may be significant in some programs.
629
630 =item --value-length=<size>
631
632 Reserved space for numbers.
633
634 =item -w, --width=<columns>
635
636 Override the maximum number of columns to use.
637 Appended graphics will extend to fill up the entire screen,
638 otherwise determined by the environment variable I<COLUMNS>
639 or by running the C<tput> command.
640
641 =item -h, --usage
642
643 Overview of available options.
644
645 =item --help
646
647 Full pod documentation
648 as rendered by perldoc.
649
650 =item -V, --version
651
652 Version information.
653
654 =back
655
656 =head1 EXAMPLES
657
658 Draw a sine wave:
659
660     seq 30 | awk '{print sin($1/10)}' | barcat
661
662 Compare file sizes (with human-readable numbers):
663
664     du -d0 -b * | barcat -H
665
666 Same from formatted results, selecting the first numeric value:
667
668     tree -s --noreport | barcat -H -f+
669
670 Compare media metadata, like image size or play time:
671
672     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
673
674     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
675
676     find -type f -print0 | xargs -0 -L1 \
677     ffprobe -show_format -of json -v error |
678     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
679
680 Memory usage of user processes with long names truncated:
681
682     ps xo rss,pid,cmd | barcat -l40
683
684 Monitor network latency from prefixed results:
685
686     ping google.com | barcat -f'time=\K' -t
687
688 Commonly used after counting, for example users on the current server:
689
690     users | tr ' ' '\n' | sort | uniq -c | barcat
691
692 Letter frequencies in text files:
693
694     cat /usr/share/games/fortunes/*.u8 |
695     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
696     sort | uniq -c | barcat
697
698 Number of HTTP requests per day:
699
700     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
701
702 Any kind of database query results, preserving returned alignment:
703
704     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
705     psql -t | barcat -u
706
707 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
708
709     > SELECT schemaname, relname, pg_total_relation_size(relid)
710       FROM pg_statio_user_tables ORDER BY idx_blks_hit
711       \g |barcat -uHf+
712
713 Same thing in SQLite (requires the sqlite3 client):
714
715     > .once |barcat -Hf+
716     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
717
718 Earthquakes worldwide magnitude 1+ in the last 24 hours:
719
720     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
721     column -ts, -n | barcat -f4 -u -l80%
722
723 External datasets, like movies per year:
724
725     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
726     jq .[].year | uniq -c | barcat
727
728 Pokémon height comparison:
729
730     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
731     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
732
733 USD/EUR exchange rate from CSV provided by the ECB:
734
735     curl https://sdw.ecb.europa.eu/export.do \
736          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
737     barcat -f',\K' --value-length=7
738
739 Total population history in XML from the World Bank:
740
741     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
742     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
743     barcat -f1 -H
744
745 Population and other information for all countries:
746
747     curl http://download.geonames.org/export/dump/countryInfo.txt |
748     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
749
750 And of course various Git statistics, such commit count by year:
751
752     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
753
754 Or the top 3 most frequent authors with statistics over all:
755
756     git shortlog -sn | barcat -L3 -s
757
758 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
759
760     ( git log --pretty=%ci --since=30day | cut -b-10
761       seq 0 30 | xargs -i date +%F -d-{}day ) |
762     sort | uniq -c | awk '$1--' | barcat --spark
763
764 Sparkline graphics of simple input given as inline parameters:
765
766     barcat -_ 3 1 4 1 5 0 9 2 4
767
768 Misusing the spark functionality to draw a lolcat line:
769
770     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
771
772 =head1 AUTHOR
773
774 Mischa POSLAWSKY <perl@shiar.org>
775
776 =head1 LICENSE
777
778 GPL3+.