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 'trim|length|l=s' => sub {
24 my ($optname, $optval) = @_;
25 $optval =~ s/%$// and $opt{trimpct}++;
26 $optval =~ m/^-?[0-9]+$/ or die(
27 "Value \"$optval\" invalid for option $optname",
28 " (number or percentage expected)\n"
36 my ($optname, $optval) = @_;
38 ($opt{hidemin}, $opt{hidemax}) =
39 $optval =~ m/\A (?: ([0-9]+)? - )? ([0-9]+)? \z/x or die(
40 "Value \"$optval\" invalid for option limit",
47 'usage|h' => sub { podexit() },
48 'help' => sub { podexit(-verbose => 2) },
49 ) or exit 64; # EX_USAGE
51 $opt{width} ||= $ENV{COLUMNS} || 80;
52 $opt{color} //= -t *STDOUT; # enable on tty
53 $opt{trim} *= $opt{width} / 100 if $opt{trimpct};
54 $opt{units} = $opt{'human-readable'} && ['', qw( k M G T P E Z Y y z a f p n μ m )];
56 if (defined $opt{interval}) {
65 $SIG{INT} = 'IGNORE'; # continue after assumed eof
68 my $anchor = !defined $opt{field} ? qr/\A/ :
69 $opt{field} =~ /^[0-9]+$/ ? qr/(?:\S*\h+){$opt{field}}\K/ :
73 s/^\h*// unless $opt{unmodified};
74 push @values, s/$anchor ( \h* -? [0-9]* \.? [0-9]+ |)/\n/x && $1;
75 if (defined $opt{trim}) {
76 my $trimpos = abs $opt{trim};
80 elsif (length > $trimpos) {
81 substr($_, $trimpos - 1) = '…';
87 $SIG{INT} = 'DEFAULT';
91 state $nr = $opt{hidemin} ? $opt{hidemin} - 1 : 0;
92 @lines and @lines > $nr or return;
94 my @order = sort { $b <=> $a } grep { length } @values;
95 my $maxval = $opt{hidemax} ? max @values[0 .. $opt{hidemax} - 1] : $order[0];
96 my $minval = min $order[-1], 0;
97 my $lenval = $opt{'value-length'} // max map { length } @order;
98 my $len = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
99 max map { length $values[$_] && length $lines[$_] }
100 0 .. min $#lines, $opt{hidemax} || (); # left padding
101 my $size = ($maxval - $minval) &&
102 ($opt{width} - $lenval - $len) / ($maxval - $minval); # bar multiplication
105 if ($opt{markers} // 1 and $size > 0) {
106 my sub orderpos { (($order[$_[0]] + $order[$_[0] + .5]) / 2 - $minval) * $size }
107 $barmark[ (sum(@order) / @order - $minval) * $size ] = '='; # average
108 $barmark[ orderpos($#order * .31731) ] = '>';
109 $barmark[ orderpos($#order * .68269) ] = '<';
110 $barmark[ orderpos($#order / 2) ] = '+'; # mean
111 $barmark[ -$minval * $size ] = '|' if $minval < 0; # zero
112 defined and $opt{color} and $_ = "\e[36m$_\e[0m" for @barmark;
114 state $lastmax = $maxval;
115 if ($maxval > $lastmax) {
116 print ' ' x ($lenval + $len);
117 printf "\e[90m" if $opt{color};
119 ($lastmax - $minval) * $size + .5,
120 '-' x (($values[$nr - 1] - $minval) * $size);
121 print "\e[92m" if $opt{color};
122 say '+' x (($maxval - $lastmax - $minval) * $size + .5);
123 print "\e[0m" if $opt{color};
129 my $unit = int(log($_[0]) / log(1000) - ($_[0] < 1));
130 sprintf "%3.1f%1s", $_[0] / 1000 ** $unit,
131 $#{$opt{units}} >> 1 < abs $unit ? "e$unit" : $opt{units}->[$unit];
134 while ($nr <= $#lines) {
135 $nr >= $opt{hidemax} and last if $opt{hidemax};
136 my $val = $values[$nr];
138 my $color = !$opt{color} ? 0 :
139 $val == $order[0] ? 32 : # max
140 $val == $order[-1] ? 31 : # min
142 $val = $opt{units} ? sival($val) : sprintf "%*s", $lenval, $val;
143 $val = "\e[${color}m$val\e[0m" if $color;
145 my $line = $lines[$nr] =~ s/\n/$val/r;
146 printf '%-*s', $len + length($val), $line;
147 print $barmark[$_] // '-' for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5;
160 barcat - graph to visualize input values
164 B<barcat> [<options>] [<input>]
168 Visualizes relative sizes of values read from input (file(s) or STDIN).
169 Contents are concatenated similar to I<cat>,
170 but numbers are reformatted and a bar graph is appended to each line.
176 =item -c, --[no-]color
178 Force colored output of values and bar markers.
179 Defaults on if output is a tty,
180 disabled otherwise such as when piped or redirected.
182 =item -f, --field=(<number>|<regexp>)
184 Compare values after a given number of whitespace separators,
185 or matching a regular expression.
187 Unspecified or I<-f0> means values are at the start of each line.
188 With I<-f1> the second word is taken instead.
189 A string can indicate the starting position of a value
190 (such as I<-f:> if preceded by colons),
191 or capture the numbers itself,
192 for example I<-f'(\d+)'> for the first digits anywhere.
194 =item -H, --human-readable
196 Format values using SI unit prefixes,
197 turning long numbers like I<12356789> into I<12.4M>.
199 =item -t, --interval[=<seconds>]
201 Interval time to output partial progress.
203 =item -l, --length=[-]<size>[%]
205 Trim line contents (between number and bars)
206 to a maximum number of characters.
207 The exceeding part is replaced by an abbreviation sign,
208 unless C<--length=0>.
210 Prepend a dash (i.e. make negative) to enforce padding
211 regardless of encountered contents.
213 =item -L, --limit=(<count>|<start>-[<end>])
215 Stop output after a number of lines.
216 All input is still counted and analyzed for statistics,
217 but disregarded for padding and bar size.
221 Statistical positions to indicate on bars.
222 Cannot be customized yet,
223 only disabled by providing an empty argument.
225 Any value enables all marker characters:
232 the sum of all values divided by the number of counted lines.
237 the middle value or average between middle values.
241 Standard deviation left of the mean.
242 Only 16% of all values are lower.
246 Standard deviation right of the mean.
247 The part between B<< <--> >> encompass all I<normal> results,
248 or 68% of all entries.
252 =item -u, --unmodified
254 Do not strip leading whitespace.
255 Keep original value alignment, which may be significant in some programs.
257 =item --value-length=<size>
259 Reserved space for numbers.
261 =item -w, --width=<columns>
263 Override the maximum number of columns to use.
264 Appended graphics will extend to fill up the entire screen.
270 Commonly used after counting, such as users on the current server:
272 users | sed 's/ /\n/g' | sort | uniq -c | barcat
274 Letter frequencies in text files:
276 cat /usr/share/games/fortunes/*.u8 |
277 perl -CO -nE 'say for grep length, split /\PL*/, uc' |
278 sort | uniq -c | barcat
280 Memory usage of user processes:
282 ps xo %mem,pid,cmd | barcat -l40
284 Sizes (in megabytes) of all root files and directories:
288 Number of HTTP requests per day:
290 cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | barcat
292 Any kind of database query with leading counts:
294 echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
297 Exchange rate USD/EUR history from CSV download provided by ECB:
299 curl https://sdw.ecb.europa.eu/export.do \
300 -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
301 grep '^[12]' | barcat -f',\K' --value-length=7
303 Total population history from the World Bank dataset (XML):
305 curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
306 xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
307 sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | barcat -f1 -H
309 Movies per year from prepared JSON data:
311 curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
312 jq '.[].year' | uniq -c | barcat
314 Pokémon height comparison:
316 curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
317 jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
319 Git statistics, such commit count by year:
321 git log --pretty=%ci | cut -b-4 | uniq -c | barcat
323 Or the most frequent authors:
325 git shortlog -sn | barcat -L3
329 ping google.com | barcat -f'time=\K' -t
333 Mischa POSLAWSKY <perl@shiar.org>