#!/usr/bin/perl -w
#
-# l / ll / lf / llf - substitute of the classic ls command
+# l / ll / lf / llf - better replacement of the classic ls command
#
# Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
#
-# Copyright: GNU General Public License
+# Perl Artistic License
use Cwd qw'abs_path';
use File::Basename;
# the name of the game
$0 =~ s:.*/::;
-$ENV{LC_CTYPE} = 'C';
+$ENV{LC_ALL} = 'C';
# unshift @ARGV,split /\s+/,$ENV{'l_opt'} if $ENV{'l_opt'};
# parse CLI arguments
$opt_l = $opt_i = $opt_t = $opt_s = $opt_a = $opt_r = $opt_d = $opt_n = 0;
$opt_L = $opt_N = $opt_c = $opt_u = $opt_S = $opt_R = $opt_z = $opt_h = 0;
-$opt_U = 0;
-${'opt_*'} = ${'opt_?'} = 0;
+$opt_U = $opt_x = $opt_E = 0;
+${'opt_*'} = 0;
$opt_m = $opt_f = $opt_F = $opt_D = '';
-&usage if !getopts('hdnlLNitcuarsUSRz*?m:f:D:F:') || $opt_h;
+getopts('hdnlLNitcuarsxUSREz*m:f:D:F:') or usage(1);
+usage(0) if $opt_h;
$opt_z = 1 unless $opt_R;
$opt_l = 1 if $0 eq 'll';
$opt_l = $opt_i = $opt_a = $opt_S = 1 if $0 eq 'lll';
+&examples if $opt_E;
if ($0 eq 'lf' or $0 eq 'llf') {
- unless ($opt_F) {
+ $opt_l = $0 eq 'llf';
+ if (scalar(@ARGV) == 0) {
+ die usage(1);
+ } elsif (scalar(@ARGV) == 1) {
$opt_F = shift;
- unless (length $opt_F) {
- print "find regexp: ";
- chomp($opt_F = <STDIN>||'');
- }
+ $opt_R = $opt_F if $opt_F eq '.';
+ } elsif (-d $ARGV[-1]) {
+ $opt_R = pop(@ARGV);
+ $opt_F = join('|',@ARGV);
+ } else {
+ $opt_F = join('|',@ARGV);
}
- $opt_l = $0 if $0 eq 'llf';
- $opt_F = '.' unless length $opt_F;
- $opt_R = $opt_F;
+ @ARGV = ();
+ @ARGV = ($opt_R) if -d $opt_R;
}
$postsort = $opt_t||$opt_s;
$postproc = $postsort||$opt_z;
-&examples if ${'opt_?'};
-
# mark for squeeze operation
$z = $opt_z ? "\0" : '';
$newer = $1;
}
}
-
+
# preselect date field number
if ($opt_c) { $sdf = 'c' }
elsif ($opt_u) { $sdf = 'a' }
$opt_z = '%'.$opt_z.'s ';
@LIST = grep { s/\0 *([,\d\.\-]+) /sprintf($opt_z,$1)/e } @LIST;
}
-
+
@LIST = reverse @LIST if $opt_r;
if (not ($opt_t or $opt_U) and grep /^d[rR-][wW-][xX-]/,@LIST) {
foreach (@LIST) { print if /^d/ }
foreach (@LIST) { print unless /^d/ }
- } else {
+ } else {
print @LIST;
}
}
print "$SS file(s):";
printf " r=%d (%s Bytes)",$SS{'-'},&d3($Ss) if $SS{'-'};
delete $SS{'-'};
- foreach my $type (qw(l d c b p s ?)) {
+ foreach my $type (qw(l d c b p s ?)) {
printf " %s=%d",$type,$SS{$type} if $SS{$type};
delete $SS{$type};
}
# collect files and build file lists
-#
+#
# INPUT: filenames
#
# GLOBAL: @LIST
my @files = @_;
my $f;
+ getacl(@files) if $opt_l and not $opt_n;
+
# loop over all argument files/directories
foreach $f (@files) {
-
+
# skip jed and emacs backup files
# next if $f =~ /~$/ and not $opt_a and not $opt_l;
-
+
# recursive?
if ($opt_R) {
list($f);
# traverse real subdirs
- if (-d $f and not -l $f) {
+ if (-d $f and not -l $f) {
$f =~ s:/*$:/:;
+ # skip other file systems on -x
+ if ($opt_x) {
+ my @pd = stat(dirname($f));
+ my @sd = stat($f);
+ next if $pd[0] ne $sd[0];
+ }
collect(getfiles($f));
}
-
+
} else {
-
+
# suppress trailing / on -d option
$f =~ s:/$:: if $opt_d;
-
+
# on trailing / list subdirs, too
- if ($f =~ m:/$:) { &list(&getfiles($f)) }
- elsif ($f eq '') { &list('/') }
+ if ($f =~ m:/$:) { list(getfiles($f)) }
+ elsif ($f eq '') { list('/') }
else {
if ($opt_L) {
unless (-e $f) {
}
list($f);
}
-
+
}
}
}
$day = $date;
$day =~ s/\s.*//;
-
+
if ($older) {
next if $older =~ /-/ and $day gt $older;
next if $older !~ /-/ and $dates{m} > time-$older;
next if $newer =~ /-/ and $day lt $newer;
next if $newer !~ /-/ and $dates{m} < time-$newer;
}
-
+
if (defined $linkname) {
# prepend sorting string
elsif ($i eq 'l') { $line .= sprintf '%3s ', $links }
elsif ($i eq 'i') { $line .= sprintf '%14s ', $inode }
elsif ($i eq 'd') { $line .= sprintf '%10s ', $date }
- elsif ($i eq 'a') { $line .= sprintf '%10s %10s %10s ',
+ elsif ($i eq 'a') { $line .= sprintf '%10s %10s %10s ',
$dates{'a'},$dates{'m'},$dates{'c'} }
} else {
+ # $mode =~ s/(....)(...)/sprintf($1.uc($2))/e if $ACL{$file};
+ substr($mode,4,3) = uc(substr($mode,4,3)) if $ACL{$file};
if ($i eq 'm') { $line .= $mode.' ' }
elsif ($i eq 'u') { $line .= sprintf '%-8s ', $uid }
elsif ($i eq 'g') { $line .= sprintf '%-8s ', $gid }
&isodate($dates{'c'}).' ' }
}
}
-
+
# predefined formats
} else {
-
+
if ($opt_n) {
- if ($opt_l) { $line .= sprintf "%06o %6d %6d $z%15s %10d ",
- $mode,$uid,$gid,$size,$date }
- else { $line .= sprintf "%06o $z%15s %10d ",
- $mode,$size,$date }
+ if ($opt_l) {
+ $line .= sprintf "%06o %6d %6d $z%15s %10d ",
+ $mode,$uid,$gid,$size,$date;
+ } else {
+ $line .= sprintf "%06o $z%15s %10d ",$mode,$size,$date;
+ }
} else {
- if ($opt_l) { $line .= sprintf "%s %-8s %-8s $z%19s %s ",
- $mode,$uid,$gid,$size,$date }
- else { $line .= sprintf "%s $z%19s %s ",
- $mode,$size,substr($date,0,-3) }
+ if ($opt_l) {
+ # $mode .= $ACL{$file} ? '+' : ' ';
+ # $mode =~ s/(....)(...)/sprintf($1.uc($2))/e if $ACL{$file};
+ substr($mode,4,3) = uc(substr($mode,4,3)) if $ACL{$file};
+ $line .= sprintf "%s %-8s %-8s $z%19s %s ",
+ $mode,$uid,$gid,$size,$date;
+ } else {
+ $line .= sprintf "%s $z%19s %s ",$mode,$size,substr($date,0,-3);
+ }
}
-
+
if ($opt_i) { $line .= sprintf '%3s %10s ',$links,$inode }
}
$line .= $linkname."\n";
-
- if ($postproc) {
+
+ if ($postproc) {
push @LIST,$line;
- } else {
+ } else {
$line =~ s/\0//;
print $line;
}
$found++;
-
+
} else {
lstat $file;
warn "$0: cannot get dir-info for ".quote($file)." - $!\n";
}
-
+
}
}
#
# INPUT: file name
#
-# OUTPUT: filename with linkname, inode, hard link count, size, mode string,
+# OUTPUT: filename with linkname, inode, hard link count, size, mode string,
# UID, GID, isodate
sub info {
my $file = shift;
if ($opt_L) { @stat = stat $file }
else { @stat = lstat $file }
-
+
if (@stat) {
-
+
$inode = $stat[1];
$bmode = $stat[2];
$links = $stat[3];
- %dates = ('m' => $stat[9],
- 'a' => $stat[8],
+ %dates = ('m' => $stat[9],
+ 'a' => $stat[8],
'c' => $stat[10]);
if ($opt_n) {
$gid = getgrgid($stat[5]) || $stat[5];
$date = &isodate($dates{$sdf});
}
-
+
if (-f _) { $type = '-'; $size = $stat[7]; }
elsif (!$opt_L && -l _) { $type = 'l'; }
elsif (-d _) { $type = 'd'; }
substr($mode,8,1) =~ tr/-x/Tt/ if -k _;
$mode = $type.$mode;
} else {
- # with short list display only effektive file access modes
- $mode = $type
- . (-r _ ? 'R' : '-')
- . (-w _ ? 'W' : '-')
- . (-x _ ? 'X' : '-');
- substr($mode,2,1) =~ tr/-x/Ss/ if -u _ or -g _;
- substr($mode,3,1) =~ tr/-x/Tt/ if -k _;
+ # with short list display only effective file access modes
+ use filetest 'access'; # respect ACLs ==> cannot use pseudofile _
+ $mode = $type
+ . (-r $file ? 'R' : '-')
+ . (-w $file ? 'W' : '-')
+ . (-x $file ? 'X' : '-');
+ substr($mode,2,1) =~ tr/-x/Ss/ if -u $file or -g $file;
+ substr($mode,3,1) =~ tr/-x/Tt/ if -k $file;
}
}
}
$size = &d3($size);
-
+
# determine longest size field
if ($opt_z) {
my $x = length $size;
}
$mode =~ s/\+$//;
#$mode .= ' ' unless $mode =~ /\+$/;
-
+
return ($linkname,$inode,$links,$size,$mode,$uid,$gid,$date,%dates);
}
+# get ACLs
+#
+# INPUT: filenames
+#
+# GLOBAL: @ACL
+sub getacl {
+ my @files;
+
+ $getfacl ||= pathsearch('getfacl') or return;
+ # warn "### @_\n";
+ foreach my $file (@_) { push @files,$file if -e $file }
+ if (@files and open my $acl,'-|',$getfacl,'-ps',@files) {
+ while (<$acl>) {
+ $ACL{$1} = $1 if /^# file: (.+)/;
+ }
+ close $acl;
+ }
+}
+
# reformat integer into 3-digit doted format
# (when non-numerical mode is set)
if (opendir D,$dir) {
$dir = '' if $dir eq '.';
- while (defined($f = readdir D)) {
-
+ while (defined($f = readdir D)) {
+
# skip . and .. pseudo-subdirs
next if $f =~ m:(^|/)\.\.?/*$:;
# skip ONTAP snapshot dir
next if $f =~ m:(^|/)\.snapshot/*$:;
-
+
# skip jed and emacs backup files
# next if $f =~ /~$/ and not $opt_a and not $opt_l;
-
+
if ($f =~ /$opt_m/) {
my $x = $dir.$f;
- if (not -l $x and -d $x and not ($opt_R or $postsort or $opt_U)) {
+ if (not -l $x and -d $x and not ($opt_R or $postsort or $opt_U)) {
push @dirs,$x;
- } else {
+ } else {
push @files,$x;
}
}
} else {
warn "$0: cannot read $dir : $!\n";
}
-
+
+ getacl(@dirs,@files) if $opt_l and not $opt_n;
return (@dirs,@files);
}
}
+sub pathsearch {
+ my $prg = shift;
+
+ foreach my $dir (split(':',$ENV{PATH})) {
+ return "$dir/$prg" if -x "$dir/$prg";
+ }
+}
+
+
# reformat timetick to ISO date string
#
# INPUT: timetick
sub quote {
local $_ = shift;
my $mc = '\'\[\]\\\\ `"$?&<>$*()|{};';
-
+
unless (defined $_) {
die "@_";
@x = caller;
sub usage {
- my $opts = '[-lastcuidnrzLRNS*] [-f format] [-D X:Y]';
- if ($0 ne 'lf') {
- print "usage: $0 $opts [-F regexp] [file...]\n";
+ my $status = shift;
+ my $opts = '[-lastcuidnrzLRxNS*] [-f format] [-D X:Y]';
+ local *OUT = $status ? *STDERR : *STDOUT;
+
+ if ($0 ne 'lf') {
+ print OUT "usage: $0 $opts [-F regexp] [file...]\n";
}
$opts =~ s/R//;
- print "usage: lf $opts regexp [directory...]\n";
- print <<EOD;
-options: -l long list
+ print OUT "usage: lf $opts regexp [regexp...] [directory]\n";
+ print OUT <<EOD;
+options: -l long list (implicit if called 'll')
-a list also .* files
-s sort by size
-t sort by time
-n numerical output
-r reverse list
-z squeeze size field (slows down output)
- -L derefernce symbolic links
+ -L show absolute real path (dereference symbolic links)
-R recursive into subdirs
+ -x do not cross filesystem boundaries with -R
-F find files matching case insensitive regexp
-N show only normal (regular) files
-S print statistics summary at end
- -* list plain file names (without masking \\)
+ -* list plain file names (without \\ masking)
-f user defined format output, format characters are:
m=mode, u=user, g=group, s=size, l=hard links count, i=inode
n=name only, d=date, a=access+modification+inodechange dates
- -D list only files newer than X and older than Y
+ -D list only files newer than X and older than Y
XY format: NUMBER[smhd] (s=seconds, m=minutes, h=hours, d=days)
XY format: YYYY-MM-DD (Y=year, M=month, D=day)
- -? show examples
+ -E show examples
EOD
- exit 2;
+ exit $status;
}
sub examples {
print <<EOD;
l *.c # list files ending with .c
l -la # list all files in long format
-l -Rrs # list files recursive reverse sorted by size
+l -Rrs # list files recursive reverse sorted by size
l -*f mus # list files native names with format: mode+user+size
l -D 10d: # list files newer than 10 days
ll # list files long format (equal to: l -l)
lll # list files extra long format (equal to: l -liS)
-lf 'status.*mp3' # list files recursive matching regexp (equal to: l -RF)
-lf sda3 /dev # list devices matching sda3 (equal to: l -RF sd3 /dev)
+lf 'status.*mp3' # list files matching regexp (equal to: l -F 'status.*mp3')
+lf sda1 /dev # list devices matching sda1 (equal to: l -RF sda1 /dev)
EOD
exit;
}