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