#!/usr/bin/perl -w # # hsort - sort "human readable" numbers... # e.g. the output of 'du -sh /home/*' # # example: du -sh /home/* | hsort > /tmp/du-sh.txt # ls -lh | tail +2 | \ # strip 'Total: 1234' line # hsort -k 5 | tail -n 15 # =head1 NAME hsort - sort "human readable" numbers =head1 SYNOPSIS BIB<]> =head1 OPTIONS =over 5 =item -f sort by field FIELD instead of field 1 (the first in a line) =item -k sort by field FIELD instead of field 1 (the first in a line), this options takes precedence if both (I<-f> and I<-k>) are given. =item -d use delimiter DELIM instead of white space, perl regexps are accepted, i.e B<[:;]> for splitting fields by colons or semicolons. =item -r reverse sort, i.e. descending order =item -s print sum of all values in FIELD at the end =back =head1 EXAMPLES # du -sh /home/* | hsort -r | less The 15 biggest files in a directory: $ ls -lh | tail +2 | \ # strip 'Total: 1234' line hsort -k 5 | tail -n 15 =head1 NOTES While sorting, each item is tested if it's numerical. If it's not, string comparison (B) is used instead of number comparison (B=E>). The summary (B<-s>) option is ignored for non numerical fields... if the field contains both (some items numerical, some not) only the numerical fields are added. =head1 DOWNLOAD L =head1 AUTHOR Hanno Hecker Ivetinari@ankh-morp.orgE> =cut use strict; use Getopt::Std; $0 =~ s#.*/##; my $VERSION = '1.1'; my %opts; my $delimiter = qr(\s+); my $field = 1; my $reverse = 0; my $summary = 0; my @lines; my $total = 0; getopts('k:f:d:rs', \%opts) or die usage(); my $file = shift @ARGV; if (!defined $file) { $file = '-'; # set to STDIN } ($delimiter = qr($opts{'d'})) if exists $opts{'d'}; ($field = $opts{'f'}) if exists $opts{'f'}; ($field = $opts{'k'}) if exists $opts{'k'}; --$field; # adjust to 0 as 1st field... don't mess around with $[ ... ! ($reverse = $opts{'r'}) if exists $opts{'r'}; ($summary = $opts{'s'}) if exists $opts{'s'}; sub ascending { ($a->[1] =~ /^\d+(\.\d+)?$/) or return ($a->[1] cmp $b->[1]); ($b->[1] =~ /^\d+(\.\d+)?$/) or return ($a->[1] cmp $b->[1]); $a->[1] <=> $b->[1]; } sub descending { ($a->[1] =~ /^\d+(\.\d+)?$/) or return ($b->[1] cmp $a->[1]); ($b->[1] =~ /^\d+(\.\d+)?$/) or return ($b->[1] cmp $a->[1]); $b->[1] <=> $a->[1]; } open FILE, $file or die "$0: Could not open '$file'\n"; @lines = (); close FILE; print map( { $_->[0] } sort { $reverse ? descending : ascending } map { [$_, hconv( (split /$delimiter/, $_)[$field] )] } @lines); if ($summary) { if ($total >= 1099511627776) { $total = sprintf("%.3fT", $total/1099511627776); } elsif ($total >= 1073741824) { $total = sprintf("%.3fG", $total/1073741824); } elsif ($total >= 1048576) { $total = sprintf("%.3fM", $total/1048576); } elsif ($total >= 1024) { $total = sprintf("%.3fK", $total/1024); } print "Total: $total\n"; } sub hconv { my @list = @_; foreach my $item (@list) { if ($item =~ s/^(\d+(\.\d+)?)T$/$1/) { $item *= 1099511627776; # 1024 * 1024 * 1024 * 1024 } elsif ($item =~ s/^(\d+(\.\d+)?)G$/$1/) { $item *= 1073741824; # 1024 * 1024 * 1024 } elsif ($item =~ s/^(\d+(\.\d+)?)M$/$1/) { $item *= 1048576; # 1024 * 1024 } elsif ($item =~ s/^(\d+(\.\d+)?)(k|K)$/$1/) { $item *= 1024; } if ($summary && $item =~ /^\d+(\.\d+)?$/) { $total += $item; } } return @list; } sub usage { return <<_END; $0: Usage: $0 [-r] [-f NR|-k NR] [-d DELIM] [FILE] $0 sorts all lines in FILE numerically... even if they have "human readable" numbers if FILE is not given, $0 reads input from stdin. -f NR - use field NR as the field with the numbers -k NR - use field NR as the field with the numbers, like sort(1). This option takes precedence over -f, if both are given. -d DELIM - use delimiter as field delimiter instead of white space -r - reverse sort _END } # vim: ts=4 sw=4 expandtab syn=perl