X-Git-Url: https://git.treefish.org/fex.git/blobdiff_plain/7fa382617fbaccc0ce522b2b3adbbee9db5ad227..refs/heads/master:/bin/l?ds=sidebyside diff --git a/bin/l b/bin/l index 93c4aca..203e310 100755 --- a/bin/l +++ b/bin/l @@ -1,10 +1,10 @@ #!/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 # -# Copyright: GNU General Public License +# Perl Artistic License use Cwd qw'abs_path'; use File::Basename; @@ -13,7 +13,7 @@ use Getopt::Std; # 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'}; @@ -22,31 +22,35 @@ $ENV{LC_CTYPE} = 'C'; # 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 = ||''); - } + $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" : ''; @@ -83,7 +87,7 @@ if ($opt_D) { $newer = $1; } } - + # preselect date field number if ($opt_c) { $sdf = 'c' } elsif ($opt_u) { $sdf = 'a' } @@ -109,13 +113,13 @@ if (@LIST && $postproc) { $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; } } @@ -125,7 +129,7 @@ if ($opt_S && $SS) { 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}; } @@ -137,7 +141,7 @@ exit ($found ? 0 : 1); # collect files and build file lists -# +# # INPUT: filenames # # GLOBAL: @LIST @@ -145,12 +149,14 @@ sub collect { 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) { @@ -165,19 +171,25 @@ sub collect { 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) { @@ -188,7 +200,7 @@ sub collect { } list($f); } - + } } } @@ -226,7 +238,7 @@ sub list { $day = $date; $day =~ s/\s.*//; - + if ($older) { next if $older =~ /-/ and $day gt $older; next if $older !~ /-/ and $dates{m} > time-$older; @@ -235,7 +247,7 @@ sub list { next if $newer =~ /-/ and $day lt $newer; next if $newer !~ /-/ and $dates{m} < time-$newer; } - + if (defined $linkname) { # prepend sorting string @@ -260,9 +272,11 @@ sub list { 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 } @@ -279,40 +293,47 @@ sub list { &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"; } - + } } @@ -320,7 +341,7 @@ sub list { # # 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; @@ -332,14 +353,14 @@ sub info { 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) { @@ -351,7 +372,7 @@ sub info { $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'; } @@ -376,13 +397,14 @@ sub info { 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; } } @@ -407,7 +429,7 @@ sub info { } $size = &d3($size); - + # determine longest size field if ($opt_z) { my $x = length $size; @@ -422,10 +444,29 @@ sub info { } $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) @@ -454,22 +495,22 @@ sub getfiles { 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; } } @@ -482,7 +523,8 @@ sub getfiles { } else { warn "$0: cannot read $dir : $!\n"; } - + + getacl(@dirs,@files) if $opt_l and not $opt_n; return (@dirs,@files); } @@ -498,6 +540,15 @@ sub nodes { } +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 @@ -518,7 +569,7 @@ sub isodate { sub quote { local $_ = shift; my $mc = '\'\[\]\\\\ `"$?&<>$*()|{};'; - + unless (defined $_) { die "@_"; @x = caller; @@ -545,14 +596,17 @@ sub fmatch { 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 <