5 use List::Util qw( min max sum );
6 use open qw( :std :utf8 );
7 use experimental qw( lexical_subs );
11 use Getopt::Long '2.33', qw( :config gnu_getopt );
14 Pod::Usage::pod2usage(-exitval => 0, -perldocopt => '-oman', @_);
19 'C' => sub { $opt{color} = 0 },
23 $opt{anchor} = /^[0-9]+$/ ? qr/(?:\S*\h+){$_}\K/ : qr/$_/;
24 } or die $@ =~ s/(?: at .+)?$/ for option $_[0]/r;
28 'trim|length|l=s' => sub {
29 my ($optname, $optval) = @_;
30 $optval =~ s/%$// and $opt{trimpct}++;
31 $optval =~ m/^-?[0-9]+$/ or die(
32 "Value \"$optval\" invalid for option $optname",
33 " (number or percentage expected)\n"
41 my ($optname, $optval) = @_;
43 ($opt{hidemin}, $opt{hidemax}) =
44 $optval =~ m/\A (?: ([0-9]+)? - )? ([0-9]+)? \z/x or die(
45 "Value \"$optval\" invalid for option limit",
53 'usage|h' => sub { podexit() },
54 'help' => sub { podexit(-verbose => 2) },
55 ) or exit 64; # EX_USAGE
57 $opt{width} ||= $ENV{COLUMNS} || 80;
58 $opt{color} //= -t *STDOUT; # enable on tty
59 $opt{trim} *= $opt{width} / 100 if $opt{trimpct};
60 $opt{units} = [split //, ' kMGTPEZYyzafpnμm'] if $opt{'human-readable'};
61 $opt{anchor} //= qr/\A/;
62 $opt{'value-length'} = 6 if $opt{units};
64 if (defined $opt{interval}) {
73 $SIG{INT} = 'IGNORE'; # continue after assumed eof
76 my $valmatch = qr/$opt{anchor} ( \h* -? [0-9]* \.? [0-9]+ (?: e[+-]?[0-9]+ )? |)/x;
79 s/^\h*// unless $opt{unmodified};
80 push @values, s/$valmatch/\n/ && $1;
81 if (defined $opt{trim}) {
82 my $trimpos = abs $opt{trim};
86 elsif (length > $trimpos) {
87 substr($_, $trimpos - 1) = '…';
93 $SIG{INT} = 'DEFAULT';
97 state $nr = $opt{hidemin} ? $opt{hidemin} - 1 : 0;
98 @lines and @lines > $nr or return;
100 @lines > $nr or return unless $opt{hidemin};
102 my @order = sort { $b <=> $a } grep { length } @values;
103 my $maxval = $opt{hidemax} ? max @values[0 .. $opt{hidemax} - 1] : $order[0];
104 my $minval = min $order[-1], 0;
105 my $lenval = $opt{'value-length'} // max map { length } @order;
106 my $len = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
107 max map { length $values[$_] && length $lines[$_] }
108 0 .. min $#lines, $opt{hidemax} || (); # left padding
109 my $size = ($maxval - $minval) &&
110 ($opt{width} - $lenval - $len) / ($maxval - $minval); # bar multiplication
113 if ($opt{markers} // 1 and $size > 0) {
114 my sub orderpos { (($order[$_[0]] + $order[$_[0] + .5]) / 2 - $minval) * $size }
115 $barmark[ (sum(@order) / @order - $minval) * $size ] = '='; # average
116 $barmark[ orderpos($#order * .31731) ] = '>';
117 $barmark[ orderpos($#order * .68269) ] = '<';
118 $barmark[ orderpos($#order / 2) ] = '+'; # mean
119 $barmark[ -$minval * $size ] = '|' if $minval < 0; # zero
120 defined and $opt{color} and $_ = "\e[36m$_\e[0m" for @barmark;
122 state $lastmax = $maxval;
123 if ($maxval > $lastmax) {
124 print ' ' x ($lenval + $len);
125 printf "\e[90m" if $opt{color};
127 ($lastmax - $minval) * $size + .5,
128 '-' x (($values[$nr - 1] - $minval) * $size);
129 print "\e[92m" if $opt{color};
130 say '+' x (($maxval - $lastmax - $minval) * $size + .5);
131 print "\e[0m" if $opt{color};
136 @lines > $nr or return if $opt{hidemin};
139 my $unit = int(log($_[0]) / log(1000) - ($_[0] < 1));
140 my $float = $_[0] !~ /^ (?: 0*\.)? [0-9]{1,3} $/x;
142 $float ? 5 : 3, $float, # length and tenths
143 $_[0] / 1000 ** $unit, # number
144 $float ? 0 : 3, # unit size
145 $#{$opt{units}} >> 1 < abs $unit ? "e$unit" : $opt{units}->[$unit]
149 while ($nr <= $#lines) {
150 $nr >= $opt{hidemax} and last if defined $opt{hidemax};
151 my $val = $values[$nr];
153 my $color = !$opt{color} ? 0 :
154 $val == $order[0] ? 32 : # max
155 $val == $order[-1] ? 31 : # min
157 $val = $opt{units} ? sival($val) : sprintf "%*s", $lenval, $val;
158 $val = "\e[${color}m$val\e[0m" if $color;
160 my $line = $lines[$nr] =~ s/\n/$val/r;
161 printf '%-*s', $len + length($val), $line;
162 print $barmark[$_] // '-' for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5;
172 printf '%d values', scalar @values;
180 barcat - graph to visualize input values
184 B<barcat> [<options>] [<input>]
188 Visualizes relative sizes of values read from input (file(s) or STDIN).
189 Contents are concatenated similar to I<cat>,
190 but numbers are reformatted and a bar graph is appended to each line.
196 =item -c, --[no-]color
198 Force colored output of values and bar markers.
199 Defaults on if output is a tty,
200 disabled otherwise such as when piped or redirected.
202 =item -f, --field=(<number>|<regexp>)
204 Compare values after a given number of whitespace separators,
205 or matching a regular expression.
207 Unspecified or I<-f0> means values are at the start of each line.
208 With I<-f1> the second word is taken instead.
209 A string can indicate the starting position of a value
210 (such as I<-f:> if preceded by colons),
211 or capture the numbers itself,
212 for example I<-f'(\d+)'> for the first digits anywhere.
214 =item -H, --human-readable
216 Format values using SI unit prefixes,
217 turning long numbers like I<12356789> into I<12.4M>.
218 Also changes an exponent I<1.602176634e-19> to I<160.2z>.
219 Short integers are aligned but kept without decimal point.
221 =item -t, --interval[=<seconds>]
223 Interval time to output partial progress.
225 =item -l, --length=[-]<size>[%]
227 Trim line contents (between number and bars)
228 to a maximum number of characters.
229 The exceeding part is replaced by an abbreviation sign,
230 unless C<--length=0>.
232 Prepend a dash (i.e. make negative) to enforce padding
233 regardless of encountered contents.
235 =item -L, --limit=(<count>|<start>-[<end>])
237 Stop output after a number of lines.
238 All input is still counted and analyzed for statistics,
239 but disregarded for padding and bar size.
243 Statistical positions to indicate on bars.
244 Cannot be customized yet,
245 only disabled by providing an empty argument.
247 Any value enables all marker characters:
254 the sum of all values divided by the number of counted lines.
259 the middle value or average between middle values.
263 Standard deviation left of the mean.
264 Only 16% of all values are lower.
268 Standard deviation right of the mean.
269 The part between B<< <--> >> encompass all I<normal> results,
270 or 68% of all entries.
276 Total statistics after all data.
278 =item -u, --unmodified
280 Do not strip leading whitespace.
281 Keep original value alignment, which may be significant in some programs.
283 =item --value-length=<size>
285 Reserved space for numbers.
287 =item -w, --width=<columns>
289 Override the maximum number of columns to use.
290 Appended graphics will extend to fill up the entire screen.
296 Commonly used after counting, such as users on the current server:
298 users | sed 's/ /\n/g' | sort | uniq -c | barcat
300 Letter frequencies in text files:
302 cat /usr/share/games/fortunes/*.u8 |
303 perl -CO -nE 'say for grep length, split /\PL*/, uc' |
304 sort | uniq -c | barcat
306 Memory usage of user processes:
308 ps xo %mem,pid,cmd | barcat -l40
310 Sizes (in megabytes) of all root files and directories:
314 Number of HTTP requests per day:
316 cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | barcat
318 Any kind of database query with leading counts:
320 echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
323 Exchange rate USD/EUR history from CSV download provided by ECB:
325 curl https://sdw.ecb.europa.eu/export.do \
326 -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
327 grep '^[12]' | barcat -f',\K' --value-length=7
329 Total population history from the World Bank dataset (XML):
331 curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
332 xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
333 sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | barcat -f1 -H
335 Movies per year from prepared JSON data:
337 curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
338 jq '.[].year' | uniq -c | barcat
340 Pokémon height comparison:
342 curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
343 jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
345 Git statistics, such commit count by year:
347 git log --pretty=%ci | cut -b-4 | uniq -c | barcat
349 Or the most frequent authors:
351 git shortlog -sn | barcat -L3
355 ping google.com | barcat -f'time=\K' -t
359 Mischa POSLAWSKY <perl@shiar.org>