delete little joke from pod description
[barcat.git] / barcat
diff --git a/barcat b/barcat
index 0cfdb2dfef64d0a69bea2e05b5551e241c43554c..753e323a0ebe376109d38f4e5f3d444334e3eb92 100755 (executable)
--- a/barcat
+++ b/barcat
@@ -26,6 +26,7 @@ GetOptions(\%opt,
                        $opt{anchor} = qr/$_/;
                } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r;
        },
+       'count|c!',
        'human-readable|H!',
        'sexagesimal!',
        'reformat!',
@@ -141,7 +142,6 @@ $opt{'graph-format'} //= '-';
 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
 $opt{units}   = [split //, ' kMGTPEZYRQqryzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
        if $opt{'human-readable'};
-$opt{anchor} //= qr/\A/;
 $opt{'value-length'} = 4 if $opt{units};
 $opt{'value-length'} = 1 if $opt{unmodified};
 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
@@ -189,7 +189,7 @@ $opt{'value-format'} = $opt{sexagesimal} ? sub {
 $opt{'value-format'} ||= sub { sprintf '%.8g', $_[0] };
 
 
-my (@lines, @values, @order);
+my (@lines, @values, @order, %uniq);
 
 $SIG{$_} = \&show_stat for $opt{'signal-stat'} || ();
 $SIG{ALRM} = sub {
@@ -205,17 +205,31 @@ if (defined $opt{interval}) {
        eval {
                require Tie::Array::Sorted;
                tie @order, 'Tie::Array::Sorted', sub { $_[1] <=> $_[0] };
-       } or warn $@, "Expect slowdown with large datasets!\n";
+       } or warn $@, "Expect slowdown with large datasets!\n"
+               unless $opt{count};
 }
 
 my $float = qr<[0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )?>; # positive numberish
-my $valmatch = qr< $opt{anchor} ( \h* -? $float |) >x;
+my $valmatch = $opt{anchor} // qr/\A/;
+$valmatch .= !$opt{count} ? qr/( \h* -? $float |)/ :
+       $opt{anchor} ? qr/(\S*)/ : qr/(.*)/;
+
 while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
        s/\r?\n\z//;
-       s/\A\h*// unless $opt{unmodified};
-       my $valnum = s/$valmatch/\n/ && $1;
-       push @values, $valnum;
-       push @order, $valnum if length $valnum;
+       my $valnum;
+       if ($opt{count}) {
+               $valnum = m/$valmatch/ && $1;
+               $uniq{$valnum}++ and next;
+               push @values, $valnum;
+               s/\A/\n /;
+       }
+       else {
+               s/\A\h*// unless $opt{unmodified};
+               $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 $valnum if $opt{unmodified};
@@ -228,6 +242,8 @@ while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
                }
        }
        push @lines, $_;
+}
+continue {
        show_lines() if defined $opt{interval} and $opt{interval} < 0
                and $. % $opt{interval} == 0;
 }
@@ -247,6 +263,11 @@ state $nr = $opt{hidemin} ? $opt{hidemin}->($#lines) : 0;
 
 my $limit = $opt{hidemax} ? $opt{hidemax}->($#lines, $nr) : $#lines;
 
+if ($opt{count}) {
+       $_ = $uniq{$_} for @values[$nr .. $limit];
+       @order = @values;
+}
+
 @order = sort { $b <=> $a } @order unless tied @order;
 my $maxval = $opt{maxval} // (
        $opt{hidemax} ? max grep { length } @values[$nr .. $limit] :
@@ -374,6 +395,7 @@ continue {
        $nr++;
 }
 say $opt{palette} ? color(0) : '' if $opt{spark};
+%uniq = () if $opt{interval} and $opt{count};
 
        return $nr;
 }
@@ -438,6 +460,8 @@ Usage:                                               /\_/\
 Options:
   -a, --[no-]ascii         Restrict user interface to ASCII characters
   -C, --[no-]color         Force colored output of values and bar markers
+  -c, --count              Omit repetitions and count the number of
+                           occurrences
   -f, --field=([+]N|REGEXP)
                            Compare values after a given number of whitespace
                            separators
@@ -485,7 +509,6 @@ Visualizes relative sizes of values read from input
 Contents are concatenated similar to I<cat>,
 but numbers are reformatted and a bar graph is appended to each line.
 
-Don't worry, barcat does not drink and divide.
 It can has various options for input and output (re)formatting,
 but remains limited to one-dimensional charts.
 For more complex graphing needs
@@ -509,6 +532,16 @@ disabled otherwise such as when piped or redirected.
 Can also be disabled by setting B<-M>
 or the I<NO_COLOR> environment variable.
 
+=item B<-c>, B<--count>
+
+Omit repetitions and count the number of occurrences.
+Similar to piping input through C<sort | uniq -c>
+but keeping the order of first appearances.
+
+Lines are omitted if they (or a specified field) are identical,
+and the amount of matches is prepended and used as values
+for bars and subsequent statistics.
+
 =item B<-f>, B<--field>=([B<+>]I<number> | I<regexp>)
 
 Compare values after a given number of whitespace separators,
@@ -742,19 +775,19 @@ Monitor network latency from prefixed results:
 
     ping google.com | barcat -f'time=\K' -t
 
-Commonly used after counting, for example users on the current server:
-
-    users | tr ' ' '\n' | sort | uniq -c | barcat
-
-Letter frequencies in text files:
+Commonly used after counting, eg letter frequencies in text files:
 
     cat /usr/share/games/fortunes/*.u8 |
     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
     sort | uniq -c | barcat
 
+Users on the current server while preserving order:
+
+    users | tr ' ' '\n' | barcat -c
+
 Number of HTTP requests per day:
 
-    cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
+    barcat -cf'\[([^:]+)' httpd/access.log
 
 Any kind of database query results, preserving returned alignment: