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]+ |)/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>.
200 =item -t, --interval[=<seconds>]
202 Interval time to output partial progress.
204 =item -l, --length=[-]<size>[%]
206 Trim line contents (between number and bars)
207 to a maximum number of characters.
208 The exceeding part is replaced by an abbreviation sign,
209 unless C<--length=0>.
211 Prepend a dash (i.e. make negative) to enforce padding
212 regardless of encountered contents.
214 =item -L, --limit=(<count>|<start>-[<end>])
216 Stop output after a number of lines.
217 All input is still counted and analyzed for statistics,
218 but disregarded for padding and bar size.
222 Statistical positions to indicate on bars.
223 Cannot be customized yet,
224 only disabled by providing an empty argument.
226 Any value enables all marker characters:
233 the sum of all values divided by the number of counted lines.
238 the middle value or average between middle values.
242 Standard deviation left of the mean.
243 Only 16% of all values are lower.
247 Standard deviation right of the mean.
248 The part between B<< <--> >> encompass all I<normal> results,
249 or 68% of all entries.
253 =item -u, --unmodified
255 Do not strip leading whitespace.
256 Keep original value alignment, which may be significant in some programs.
258 =item --value-length=<size>
260 Reserved space for numbers.
262 =item -w, --width=<columns>
264 Override the maximum number of columns to use.
265 Appended graphics will extend to fill up the entire screen.
271 Commonly used after counting, such as users on the current server:
273 users | sed 's/ /\n/g' | sort | uniq -c | barcat
275 Letter frequencies in text files:
277 cat /usr/share/games/fortunes/*.u8 |
278 perl -CO -nE 'say for grep length, split /\PL*/, uc' |
279 sort | uniq -c | barcat
281 Memory usage of user processes:
283 ps xo %mem,pid,cmd | barcat -l40
285 Sizes (in megabytes) of all root files and directories:
289 Number of HTTP requests per day:
291 cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | barcat
293 Any kind of database query with leading counts:
295 echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
298 Exchange rate USD/EUR history from CSV download provided by ECB:
300 curl https://sdw.ecb.europa.eu/export.do \
301 -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
302 grep '^[12]' | barcat -f',\K' --value-length=7
304 Total population history from the World Bank dataset (XML):
306 curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
307 xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
308 sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | barcat -f1 -H
310 Movies per year from prepared JSON data:
312 curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
313 jq '.[].year' | uniq -c | barcat
315 Pokémon height comparison:
317 curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
318 jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
320 Git statistics, such commit count by year:
322 git log --pretty=%ci | cut -b-4 | uniq -c | barcat
324 Or the most frequent authors:
326 git shortlog -sn | barcat -L3
330 ping google.com | barcat -f'time=\K' -t
334 Mischa POSLAWSKY <perl@shiar.org>