X-Git-Url: http://git.shiar.nl/barcat.git/blobdiff_plain/628cc78d7f81eaad4f0a06a26afc171d2667e685..efe8f223b69129acecbdfd6093c9c0dff178633c:/barcat diff --git a/barcat b/barcat index ad6ccab..a86f74f 100755 --- a/barcat +++ b/barcat @@ -1,30 +1,39 @@ #!/usr/bin/perl -CA -use 5.018; +use 5.014; use warnings; use utf8; use List::Util qw( min max sum ); use open qw( :std :utf8 ); -use experimental qw( lexical_subs ); +use re '/msx'; -our $VERSION = '1.06'; +our $VERSION = '1.09'; -use Getopt::Long '2.33', qw( :config gnu_getopt ); my %opt; +if (@ARGV) { +require Getopt::Long; +Getopt::Long->import('2.33', qw( :config gnu_getopt )); GetOptions(\%opt, - 'color|c!', - 'C' => sub { $opt{color} = 0 }, + 'ascii|a!', + 'color|C!', + 'M' => sub { $opt{color} = 0 }, 'field|f=s' => sub { eval { local $_ = $_[1]; - $opt{anchor} = /^[0-9]+$/ ? qr/(?:\S*\h+){$_}\K/ : qr/$_/; - } or die $@ =~ s/(?: at .+)?$/ for option $_[0]/r; + s/\A[0-9]+\z/(?:\\S*\\h+){$_}\\K/; + s{\A[+]([0-9]*)\z}{ + (!!$1 && '(?:\d+\D+\b){'.$1.'}\K') . '\s* (?=\d)' + }e; + $opt{anchor} = qr/$_/; + } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r; }, 'human-readable|H!', + 'sexagesimal!', + 'reformat!', 'interval|t:i', 'trim|length|l=s' => sub { my ($optname, $optval) = @_; $optval =~ s/%$// and $opt{trimpct}++; - $optval =~ m/^-?[0-9]+$/ or die( + $optval =~ m/\A-?[0-9]+\z/ or die( "Value \"$optval\" invalid for option $optname", " (number or percentage expected)\n" ); @@ -35,51 +44,63 @@ GetOptions(\%opt, 'hidemax=i', 'minval=f', 'maxval=f', - 'limit|L=s' => sub { + 'limit|L:s' => sub { my ($optname, $optval) = @_; $optval ||= 0; + $optval =~ /\A-[0-9]+\z/ and $optval .= '-'; # tail shorthand ($opt{hidemin}, $opt{hidemax}) = - $optval =~ m/\A (?: ([0-9]+)? - )? ([0-9]+)? \z/x or die( + $optval =~ m/\A (?: (-? [0-9]+)? - )? ([0-9]+)? \z/ or die( "Value \"$optval\" invalid for option limit", " (range expected)\n" ); }, + 'log|e!', 'header!', 'markers|m=s', 'graph-format=s' => sub { $opt{'graph-format'} = substr $_[1], 0, 1; }, - 'spark:s' => sub { - $opt{spark} = [split //, $_[1] || '▁▂▃▄▅▆▇█']; - }, + 'spark|_!', + 'indicators:s', 'palette=s' => sub { $opt{palette} = { + '' => [], fire => [qw( 90 31 91 33 93 97 96 )], + fire256=> [map {"38;5;$_"} qw( + 235 52 88 124 160 196 + 202 208 214 220 226 227 228 229 230 231 159 + )], whites => [qw( 1;30 0;37 1;37 )], - }->{$_[1]} // [ split /\s/, $_[1] ]; + greys => [map {"38;5;$_"} 0, 232..255, 15], + random => [map {"38;5;$_"} List::Util::shuffle(17..231)], + rainbow=> [map {"38;5;$_"} + 196, # r + (map { 196 + $_*6 } 0..4), # +g + (map { 226 - $_*6*6 } 0..4), # -r + (map { 46 + $_ } 0..4), # +b + (map { 51 - $_*6 } 0..4), # -g + (map { 21 + $_*6*6 } 0..4), # +r + (map { 201 - $_ } 0..4), # -b + 196, + ], + }->{$_[1]} // do { + my @vals = split /[^0-9;]/, $_[1] + or die "Empty palette resulting from \"$_[1]\"\n"; + \@vals; + }; }, 'stat|s!', + 'report=s', 'signal-stat=s', 'unmodified|u!', 'width|w=i', - 'version' => sub { - say "barcat version $VERSION"; + 'version|V' => sub { + my $mascot = $opt{ascii} ? '=^,^=' : 'ฅ^•ﻌ•^ฅ'; + say "barcat $mascot version $VERSION"; exit; }, 'usage|h' => sub { - local $/; - my $pod = readline *DATA; - $pod =~ s/^=over\K/ 22/m; # indent options list - $pod =~ s/^=item \N*\n\n\N*\n\K(?:(?:^=over.*?^=back\n)?(?!=)\N*\n)*/\n/msg; - - require Pod::Usage; - my $parser = Pod::Usage->new; - $parser->select('SYNOPSIS', 'OPTIONS'); - $parser->output_string(\my $contents); - $parser->parse_string_document($pod); - - $contents =~ s/\n(?=\n\h)//msg; # strip space between items - print $contents; + /^=/ ? last : print for readline *DATA; # text between __END__ and pod exit; }, 'help|?' => sub { @@ -89,18 +110,56 @@ GetOptions(\%opt, ); }, ) or exit 64; # EX_USAGE +} -$opt{width} ||= $ENV{COLUMNS} || 80; -$opt{color} //= -t *STDOUT; # enable on tty +$opt{width} ||= $ENV{COLUMNS} || qx(tput cols) || 80 unless $opt{spark}; +$opt{color} //= $ENV{NO_COLOR} ? 0 : -t *STDOUT; # enable on tty $opt{'graph-format'} //= '-'; $opt{trim} *= $opt{width} / 100 if $opt{trimpct}; -$opt{units} = [split //, ' kMGTPEZYyzafpnμm'] if $opt{'human-readable'}; +$opt{units} = [split //, ' kMGTPEZYRQqryzafpn'.($opt{ascii} ? 'u' : 'μ').'m'] + if $opt{'human-readable'}; $opt{anchor} //= qr/\A/; -$opt{'value-length'} = 6 if $opt{units}; +$opt{'value-length'} = 4 if $opt{units}; $opt{'value-length'} = 1 if $opt{unmodified}; $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT'; $opt{markers} //= '=avg >31.73v <68.27v +50v |0'; +$opt{report} //= join(', ', + '${min; color(31)} min', + '${avg; $opt{reformat} or $_ = sprintf "%0.2f", $_; color(36)} avg', + '${max; color(32)} max', +); $opt{palette} //= $opt{color} && [31, 90, 32]; +$opt{indicators} = [split //, $opt{indicators} || + ($opt{ascii} ? ' .oO' : $opt{spark} ? ' ▁▂▃▄▅▆▇█' : ' ▏▎▍▌▋▊▉█') +] if defined $opt{indicators} or $opt{spark}; +$opt{hidemin} = ($opt{hidemin} || 1) - 1; +$opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef + and undef $opt{interval}; + +$opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] }; +$opt{'value-format'} = $opt{sexagesimal} ? sub { + my $s = abs($_[0]) + .5; + sprintf('%s%d:%02d:%02d', $_[0] < 0 && '-', $s/3600, $s/60%60, $s%60); +} : $opt{units} && sub { + my $unit = ( + log(abs $_[0] || 1) / log(10) + - 3 * (abs($_[0]) < .9995) # shift to smaller unit if below 1 + + 1e-15 # float imprecision + ); + my $decimal = ($unit % 3) == ($unit < 0); + $unit -= log($decimal ? .995 : .9995) / log(10); # rounded + $decimal = ($unit % 3) == ($unit < 0); + $decimal &&= $_[0] !~ /^-?0*[0-9]{1,3}$/; # integer 0..999 + sprintf('%*.*f%1s', + 3 + ($_[0] < 0), # digits plus optional negative sign + $decimal, # tenths + $_[0] / 1000 ** int($unit/3), # number + $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) : + $opt{units}->[$unit/3] # suffix + ); +} and $opt{reformat}++; +$opt{'value-format'} ||= sub { sprintf '%.8g', $_[0] }; + my (@lines, @values, @order); @@ -121,20 +180,24 @@ if (defined $opt{interval}) { } or warn $@, "Expect slowdown with large datasets!\n"; } -my $valmatch = qr/$opt{anchor} ( \h* -? [0-9]* \.? [0-9]+ (?: e[+-]?[0-9]+ )? |)/x; -while (readline) { +my $valmatch = qr< + $opt{anchor} ( \h* -? [0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )? |) +>x; +while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) { s/\r?\n\z//; - s/^\h*// unless $opt{unmodified}; - push @values, s/$valmatch/\n/ && $1; - push @order, $1 if length $1; - if (defined $opt{trim} and defined $1) { + s/\A\h*// unless $opt{unmodified}; + my $valnum = s/$valmatch/\n/ && $1; + push @values, $valnum; + push @order, $valnum if length $valnum; + if (defined $opt{trim} and defined $valnum) { my $trimpos = abs $opt{trim}; - $trimpos -= length $1 if $opt{unmodified}; + $trimpos -= length $valnum if $opt{unmodified}; if ($trimpos <= 1) { $_ = substr $_, 0, 2; } elsif (length > $trimpos) { - substr($_, $trimpos - 1) = '…'; + # cut and replace (intentional lvalue for speed, contrary to PBP) + substr($_, $trimpos - 1) = $opt{ascii} ? '>' : '…'; } } push @lines, $_; @@ -152,20 +215,35 @@ sub color { sub show_lines { -state $nr = $opt{hidemin} ? $opt{hidemin} - 1 : 0; -@lines and @lines > $nr or return; -@lines or return; -@lines > $nr or return unless $opt{hidemin}; +state $nr = + $opt{hidemin} < 0 ? max(0, @lines + $opt{hidemin} + 1) : + $opt{hidemin}; +@lines > $nr or return; + +my $limit = $#lines; +if (defined $opt{hidemax}) { + if ($opt{hidemin} and $opt{hidemin} < 0) { + $limit -= $opt{hidemax} - 1; + } + elsif ($opt{hidemax} <= $limit) { + $limit = $opt{hidemax} - 1; + } +} @order = sort { $b <=> $a } @order unless tied @order; -my $maxval = $opt{maxval} // ($opt{hidemax} ? max grep { length } @values[0 .. $opt{hidemax} - 1] : $order[0]) // 0; +my $maxval = $opt{maxval} // ( + $opt{hidemax} ? max grep { length } @values[$nr .. $limit] : + $order[0] +) // 0; my $minval = $opt{minval} // min $order[-1] // (), 0; +my $range = $maxval - $minval; +$range &&= log $range if $opt{log}; my $lenval = $opt{'value-length'} // max map { length } @order; my $len = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 : max map { length $values[$_] && length $lines[$_] } 0 .. min $#lines, $opt{hidemax} || (); # left padding -my $size = ($maxval - $minval) && - ($opt{width} - $lenval - $len) / ($maxval - $minval); # bar multiplication +my $size = defined $opt{width} && $range && + ($opt{width} - $lenval - $len - !!$opt{indicators}); # bar multiplication my @barmark; if ($opt{markers} and $size > 0) { @@ -176,15 +254,27 @@ if ($opt{markers} and $size > 0) { return sum(@order) / @order; } elsif ($func =~ /\A([0-9.]+)v\z/) { + $1 <= 100 or die( + "Invalid marker $char: percentile $1 out of bounds\n" + ); my $index = $#order * $1 / 100; return ($order[$index] + $order[$index + .5]) / 2; } - else { + elsif ($func =~ /\A-?[0-9.]+\z/) { return $func; } - } - $minval; + else { + die "Unknown marker $char: $func\n"; + } + }; + defined $pos or do { + warn $@ if $@; + next; + }; + $pos -= $minval; + $pos &&= log $pos if $opt{log}; $pos >= 0 or next; - color(36) for $barmark[$pos * $size] = $char; + color(36) for $barmark[$pos / $range * $size] = $char; } state $lastmax = $maxval; @@ -192,57 +282,65 @@ if ($opt{markers} and $size > 0) { print ' ' x ($lenval + $len); printf color(90); printf '%-*s', - ($lastmax - $minval) * $size + .5, - '-' x (($values[$nr - 1] - $minval) * $size); + ($lastmax - $minval) * $size / $range + .5, + '-' x (($values[$nr - 1] - $minval) * $size / $range); print color(92); - say '+' x (($maxval - $lastmax - $minval) * $size + .5); + say '+' x (($range - $lastmax) * $size / $range + .5); print color(0); $lastmax = $maxval; } } -@lines > $nr or return if $opt{hidemin}; - -sub sival { - my $unit = int(log(abs $_[0] || 1) / log(10) - 3*($_[0] < 1) + 1e-15); - my $float = $_[0] !~ /^0*[-0-9]{1,3}$/; - sprintf('%3.*f%1s', - $float && ($unit % 3) == ($unit < 0), # tenths - $_[0] / 1000 ** int($unit/3), # number - $#{$opt{units}} * 1.5 < abs $unit ? "e$unit" : $opt{units}->[$unit/3] - ); -} - say( color(31), sprintf('%*s', $lenval, $minval), color(90), '-', color(36), '+', - color(32), sprintf('%*s', $size * ($maxval - $minval) - 3, $maxval), + color(32), sprintf('%*s', $size - 3, $maxval), color(90), '-', color(36), '+', color(0), ) if $opt{header}; -while ($nr <= $#lines) { - $nr >= $opt{hidemax} and last if defined $opt{hidemax}; +while ($nr <= $limit) { my $val = $values[$nr]; - my $rel = length $val && ($val - $minval) / ($maxval - $minval); + my $rel; + if (length $val) { + $rel = $val - $minval; + $rel &&= log $rel if $opt{log}; + $rel = min(1, $rel / $range) if $range; # 0..1 + } + my $color = !length $val || !$opt{palette} ? undef : + $val == $order[0] ? $opt{palette}->[-1] : # max + $val == $order[-1] ? $opt{palette}->[0] : # min + $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ]; + my $indicator = $opt{indicators} && $opt{indicators}->[ + !length($val) || !$#{$opt{indicators}} ? 0 : # blank + $#{$opt{indicators}} < 2 ? 1 : + $val >= $order[0] ? -1 : + $rel * ($#{$opt{indicators}} - 1e-14) + 1 + ]; if ($opt{spark}) { - print color($opt{palette}->[ $rel * $#{$opt{palette}} ]) if $opt{palette}; - print $opt{spark}->[ $rel * $#{$opt{spark}} ]; + say '' if $opt{width} and $nr and $nr % $opt{width} == 0; + print color($color), $_ for $indicator; next; } + print $indicator if defined $indicator; if (length $val) { - my $color = !$opt{palette} ? undef : - $val == $order[0] ? $opt{palette}->[-1] : # max - $val == $order[-1] ? $opt{palette}->[0] : # min - $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ]; - $val = $opt{units} ? sival($val) : sprintf "%*s", $lenval, $val; + $val = sprintf("%*s", $lenval, + $opt{reformat} ? $opt{'value-format'}->($val) : $val + ); color($color) for $val; } my $line = $lines[$nr] =~ s/\n/$val/r; + if (not length $val) { + say $line; + next; + } printf '%-*s', $len + length($val), $line; - print $barmark[$_] // $opt{'graph-format'} for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5; + if ($rel and $size) { + print $barmark[$_] // $opt{'graph-format'} + for 1 .. $rel * $size + .5; + } say ''; } continue { @@ -250,25 +348,59 @@ continue { } say $opt{palette} ? color(0) : '' if $opt{spark}; + return $nr; } sub show_stat { if ($opt{hidemin} or $opt{hidemax}) { - $opt{hidemin} ||= 1; - $opt{hidemax} ||= @lines; - printf '%s of ', sum(@values[$opt{hidemin} - 1 .. $opt{hidemax} - 1]) // 0; + my $linemin = $opt{hidemin}; + my $linemax = ($opt{hidemax} || @lines) - 1; + if ($linemin < 0) { + $linemin += @lines; + $linemax = @lines - $linemax; + } + printf '%.8g of ', $opt{'value-format'}->( + sum(grep {length} @values[$linemin .. $linemax]) // 0 + ); } if (@order) { my $total = sum @order; - printf '%s total', color(1) . $total . color(0); - printf ' in %d values', scalar @values; - printf(' (%s min, %s avg, %s max)', - color(31) . $order[-1] . color(0), - color(36) . (sprintf '%*.*f', 0, 2, $total / @order) . color(0), - color(32) . $order[0] . color(0), - ); + my $fmt = '${sum;color(1)} total in ${count#} values'; + $fmt .= ' over ${lines#} lines' if @order != @lines; + $fmt .= " ($_)" for $opt{report} || (); + print varfmt($fmt, { + sum => $total, + count => int @order, + lines => int @lines, + min => $order[-1], + max => $order[0], + avg => $total / @order, + }); } say ''; + return 1; +} + +sub varfmt { + my ($fmt, $vars) = @_; + $fmt =~ s[\$\{( (?: [^{}]++ | \{(?1)\} )+ )\}]{ + my ($name, $cmd) = split /\s*;/, $1, 2; + my $format = $name !~ s/\h*\#// && $opt{reformat}; + local $_ = $vars->{$name}; + if (defined) { + $_ = $opt{'value-format'}->($_) if $format; + if ($cmd) { + eval $cmd; + warn "Error in \$$name report: $@" if $@; + } + $_; + } + else { + warn "Unknown variable \$$name in report\n"; + "\$$name"; + } + }eg; + return $fmt; } sub show_exit { @@ -281,19 +413,56 @@ sub show_exit { show_exit(); __END__ +Usage: /\_/\ + barcat [OPTIONS] [FILES|NUMBERS] (=•.•=) + (u u) +Options: + -a, --[no-]ascii Restrict user interface to ASCII characters + -C, --[no-]color Force colored output of values and bar markers + -f, --field=([+]N|REGEXP) + Compare values after a given number of whitespace + separators + --header Prepend a chart axis with minimum and maximum + values labeled + -H, --human-readable Format values using SI unit prefixes + --sexagesimal Convert seconds to HH:MM:SS time format + -t, --interval[=(N|-LINES)] + Output partial progress every given number of + seconds or input lines + -l, --length=[-]SIZE[%] Trim line contents (between number and bars) + -L, --limit[=(N|-LAST|START-[END])] + Stop output after a number of lines + -e, --log Logarithmic (exponential) scale instead of linear + --graph-format=CHAR Glyph to repeat for the graph line + -m, --markers=FORMAT Statistical positions to indicate on bars + --min=N, --max=N Bars extend from 0 or the minimum value if lower + --palette=(PRESET|COLORS) + Override colors of parsed numbers + -_, --spark Replace lines by sparklines + --indicators[=CHARS] Prefix a unicode character corresponding to each + value + -s, --stat Total statistics after all data + -u, --unmodified Do not reformat values, keeping leading whitespace + --value-length=SIZE Reserved space for numbers + -w, --width=COLUMNS Override the maximum number of columns to use + -h, --usage Overview of available options + --help Full pod documentation + -V, --version Version information + =encoding utf8 =head1 NAME -barcat - graph to visualize input values +barcat - concatenate texts with graph to visualize values =head1 SYNOPSIS -B [] [] +B [] [... | ] =head1 DESCRIPTION -Visualizes relative sizes of values read from input (file(s) or STDIN). +Visualizes relative sizes of values read from input +(parameters, file(s) or STDIN). Contents are concatenated similar to I, but numbers are reformatted and a bar graph is appended to each line. @@ -307,13 +476,21 @@ you'll need a larger animal like I. =over -=item -c, --[no-]color +=item -a, --[no-]ascii + +Restrict user interface to ASCII characters, +replacing default UTF-8 by their closest approximation. +Input is always interpreted as UTF-8 and shown as is. + +=item -C, --[no-]color Force colored output of values and bar markers. Defaults on if output is a tty, disabled otherwise such as when piped or redirected. +Can also be disabled by setting I<-M> +or the I environment variable. -=item -f, --field=(|) +=item -f, --field=([+] | ) Compare values after a given number of whitespace separators, or matching a regular expression. @@ -324,6 +501,7 @@ A string can indicate the starting position of a value (such as I<-f:> if preceded by colons), or capture the numbers itself, for example I<-f'(\d+)'> for the first digits anywhere. +A shorthand for this is I<+0>, or I<+N> to find the Nth number. =item --header @@ -336,7 +514,11 @@ turning long numbers like I<12356789> into I<12.4M>. Also changes an exponent I<1.602176634e-19> to I<160.2z>. Short integers are aligned but kept without decimal point. -=item -t, --interval[=(|-)] +=item --sexagesimal + +Convert seconds to HH:MM:SS time format. + +=item -t, --interval[=( | -)] Output partial progress every given number of seconds or input lines. An update can also be forced by sending a I alarm signal. @@ -351,12 +533,21 @@ unless C<--length=0>. Prepend a dash (i.e. make negative) to enforce padding regardless of encountered contents. -=item -L, --limit=(|-[]) +=item -L, --limit[=( | - | -[])] Stop output after a number of lines. +A single value indicates the last line number (like C), +or first line counting from the bottom if negative (like C). +A specific range can be given by two values. + All input is still counted and analyzed for statistics, but disregarded for padding and bar size. +=item -e, --log + +Logarithmic (Ixponential) scale instead of linear +to compare orders of magnitude. + =item --graph-format= Glyph to repeat for the graph line. @@ -403,22 +594,28 @@ These options can be set to customize this range. Override colors of parsed numbers. Can be any CSI escape, such as I<90> for default dark grey, -or alternatively I<1;30> for bold black. +or alternatively I<1;30> for bright black. In case of additional colors, the last is used for values equal to the maximum, the first for minima. If unspecified, these are green and red respectively (I<31 90 32>). +Multiple intermediate colors will be distributed +relative to the size of values. -=item --spark[=] +Predefined color schemes are named I and I, +or I and I for 256-color variants. + +=item -_, --spark Replace lines by I, -single characters corresponding to input values. -A specified sequence of unicode characters will be used for -Of a specified sequence of unicode characters, -the first one will be used for non-values, -the last one for the maximum, -the second (if any) for the minimum, -and any remaining will be distributed over the range of values. +single characters (configured by C<--indicators>) +corresponding to input values. + +=item --indicators[=] + +Prefix a unicode character corresponding to each value. +The first specified character will be used for non-values, +the remaining sequence will be distributed over the range of values. Unspecified, block fill glyphs U+2581-2588 will be used. =item -s, --stat @@ -437,7 +634,9 @@ Reserved space for numbers. =item -w, --width= Override the maximum number of columns to use. -Appended graphics will extend to fill up the entire screen. +Appended graphics will extend to fill up the entire screen, +otherwise determined by the environment variable I +or by running the C command. =item -h, --usage @@ -445,10 +644,10 @@ Overview of available options. =item --help -Full documentation -rendered by perldoc. +Full pod documentation +as rendered by perldoc. -=item --version +=item -V, --version Version information. @@ -464,9 +663,23 @@ Compare file sizes (with human-readable numbers): du -d0 -b * | barcat -H +Same from formatted results, selecting the first numeric value: + + tree -s --noreport | barcat -H -f+ + +Compare media metadata, like image size or play time: + + exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat + + exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal + + find -type f -print0 | xargs -0 -L1 \ + ffprobe -show_format -of json -v error | + jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex + Memory usage of user processes with long names truncated: - ps xo %mem,pid,cmd | barcat -l40 + ps xo rss,pid,cmd | barcat -l40 Monitor network latency from prefixed results: @@ -474,7 +687,7 @@ Monitor network latency from prefixed results: Commonly used after counting, for example users on the current server: - users | sed 's/ /\n/g' | sort | uniq -c | barcat + users | tr ' ' '\n' | sort | uniq -c | barcat Letter frequencies in text files: @@ -484,43 +697,55 @@ Letter frequencies in text files: Number of HTTP requests per day: - cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | barcat + cat httpd/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | barcat -Any kind of database query with counts, preserving returned alignment: +Any kind of database query results, preserving returned alignment: - echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' | + echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' | psql -t | barcat -u +In PostgreSQL from within the client; a fancy C<\dt+> perhaps: + + > SELECT schemaname, relname, pg_total_relation_size(relid) + FROM pg_statio_user_tables ORDER BY idx_blks_hit + \g |barcat -uHf+ + +Same thing in SQLite (requires the sqlite3 client): + + > .once |barcat -Hf+ + > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1; + Earthquakes worldwide magnitude 1+ in the last 24 hours: - https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv | - column -tns, | graph -f4 -u -l80% + curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv | + column -ts, -n | barcat -f4 -u -l80% External datasets, like movies per year: - curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json | - perl -054 -nlE 'say if s/^"year"://' | uniq -c | barcat - -But please get I to process JSON -and replace the manual selection by C<< jq '.[].year' >>. + curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L | + jq .[].year | uniq -c | barcat Pokémon height comparison: - curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json | + curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L | jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat USD/EUR exchange rate from CSV provided by the ECB: curl https://sdw.ecb.europa.eu/export.do \ -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' | - grep '^[12]' | barcat -f',\K' --value-length=7 + barcat -f',\K' --value-length=7 -Total population history from the World Bank dataset (XML): -External datasets, like total population in XML from the World Bank: +Total population history in XML from the World Bank: curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL | - xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - | - sed -r 's,,\n,g; s,(<[^>]+>)+, ,g' | barcat -f1 -H + xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n | + barcat -f1 -H + +Population and other information for all countries: + + curl http://download.geonames.org/export/dump/countryInfo.txt | + grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s And of course various Git statistics, such commit count by year: @@ -530,11 +755,19 @@ Or the top 3 most frequent authors with statistics over all: git shortlog -sn | barcat -L3 -s -Activity of the last days (substitute date C<-v-{}d> on BSD): +Activity graph of the last days (substitute date C<-v-{}d> on BSD): ( git log --pretty=%ci --since=30day | cut -b-10 seq 0 30 | xargs -i date +%F -d-{}day ) | - sort | uniq -c | awk '$1--' | graph --spark + sort | uniq -c | awk '$1--' | barcat --spark + +Sparkline graphics of simple input given as inline parameters: + + barcat -_ 3 1 4 1 5 0 9 2 4 + +Misusing the spark functionality to draw a lolcat line: + + seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow =head1 AUTHOR