]> git.treefish.org Git - fex.git/blobdiff - bin/l
Original release 20160919
[fex.git] / bin / l
diff --git a/bin/l b/bin/l
index 93c4aca0307ac4c6d12055095834fa4816c87b0b..203e3101069f1976ca9261f8a180ba2bfdaa28c2 100755 (executable)
--- 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 <framstag@rus.uni-stuttgart.de>
 #
-# 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 = <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" : '';
 
@@ -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 <<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
@@ -564,34 +618,35 @@ options: -l  long list
          -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;
 }