$ perl find1.pl
Use of uninitialized value in numeric gt (>) at find1.pl line 7.
Use of uninitialized value in numeric gt (>) at find1.pl line 7.
Use of uninitialized value in numeric gt (>) at find1.pl line 7.
/usr/share/man/man1 Tue Mar 22 17:08:00 2011
$ cat find1.pl
#! usr/bin/perl -w
use strict;
use File::Find;
@ARGV = ('/usr/share/man/man1') unless @ARGV;
my ($age, $name);
sub youngest {
return if defined $age&& $age> -M;
$age = (stat(_))[9];
$name = $File::Find::name;
}
find(\&youngest, @ARGV);
print "$name " . scalar(localtime($age)) . "\n";
$
The most interesting bug in the script is that you're comparing a value
returned by -M to a value (in $age) that came from (stat(_))[9]. Those are
both modification times, but one of them measured in seconds, counting
forward from 1970, and the other one is measured in days, counting backward
from the time the script started running. Pick one of those and use it
consistently. (I'd pick stat. -M is just too weird for anything but golfing)
Sorry about that Alan, that seems to have a been a typo, and if that's
the way it's been going for you out on the links, then I'm sorry for
your golf game too. I'm breaking my frisbee game down completely this
season, so far with 0 success except no elbow injury.
The next bug is hinted at by the uninitialized value warnings. Since you've
tested $age for definedness before comparing, the only thing that can be
causing the warning is that -M is returning undef. You must have some
dangling symlinks in that directory. To make your script handle dangling
symlinks correctly, you could use (lstat)[9] or you could check for the undef
timestamp and just skip them. Depends on how you want to handle symlinks in
general. File::Find has some options related to symlink following that you
might want to enable.
dangling_symlinks
If true and a code reference, will be called with the symbolic link
name and the directory it lives in as arguments. Otherwise, if true and
warnings are on, warning "symbolic_link_name is a dangling symbolic
link\n" will be issued. If false, the dangling symbolic link will be
silently ignored.
Wow, the densely packed misconceptions! First of all, in File::Find, just as
in find(1), the "directory itself" is a legitimate part of the search output.
There's no reason why "/usr/share/man/man1" can't be the answer to "what is
the youngest file found under /usr/share/man/man1?"... Directories are files
too. If you want to exclude them, put "return if -d" at the top of your
filter function.
I can pack them in can't I? I'm very confused still:
$ cat find2.pl
#! usr/bin/perl -w
use strict;
use File::Find;
@ARGV = (".") unless @ARGV;
my ($age, $name);
sub youngest {
return if -d;
$age = (stat(_))[9];
$name = $File::Find::name;
}
find(\&youngest, @ARGV);
print "$name " . scalar(localtime($age)) . "\n";
$ ls -t
find2.pl text1 uvi rmtree2 rename fdirs
lst2 find1.pl symirror rmtree1 lst ch09.code
$ perl find2.pl
../text1 Wed Mar 23 22:05:26 2011
$
To my eye, the "right" answer is find2.pl. Is there something about a
file not being able to see itself?
Secondly, -M and (stat)[9] are modification times, not birth times. It's
easily possible for a directory to be newer than any file inside it, since a
directory is modified whenever a file is added to the directory or removed
from it.
I see.
The time the directory was "born" would not be recorded in a traditional unix
filesystem. Recently some unix filesystems have been created which do record
"birthtime", but since they were not around when perl was invented, perl
doesn't have an easy way of accessing those timestamps. (None that I can find
anyway - looking under -X and stat in perlfunc(1) and also at File::stat(3) I
don't see any mention of birthtime)
If birthtime of the files is what you're after, you'll probably find
modification time to be a poor substitute. I have an installed man page with
a modification time of 15:51:31, December 10, 1992, several times older than
the computer I'm using. Imagine the path that file has taken to get to me! It
must have been compressed and uncompressed with algorithms no longer used,
passed around from FTP mirror to FTP mirror on OSes no longer remembered,
copied from hard drive to tape, CD, and DVD and back a few times, put into
tarballs and other containers and taken back out again, and through all that
it was never modified.
The point of that story is: mtime applies to file contents, not file
location. The mtime of an installed man page is likely to be the time that
it was last modified by the author, not the timestamp of installation on your
machine.
Gotcha.
In cases like that, ctime can be a better substitute, as long as you don't do
a lot of chmod'ing of things after they're installed.
Oh and just use ls -ltr next time.
Thanks, Alan, that helps for sure. I've been looking at some of the
issues he talked about in light of the lst program in chp. 9.13 of _Perl
Cookbook_.
$ perl lst -l /usr/share/man >text2
These are the oldest files there:
1000886 0644 1 root root 6096 Wed Oct 10 09:25:04 2007
/usr/share/man/man1/readom.1.gz
1000250 0644 1 root root 3460 Wed Nov 22 09:56:03 2006
/usr/share/man/man3/IO::String.3pm.gz
1004123 0644 1 root root 562 Tue Oct 26 10:33:49 2004
/usr/share/man/man8/install-sgmlcatalog.8.gz
1004444 0644 1 root root 704 Tue Oct 26 10:33:49 2004
/usr/share/man/man8/update-catalog.8.gz
1002021 0644 1 root root 4013 Wed Jul 28 14:36:05 2004
/usr/share/man/man3/Tie::IxHash.3pm.gz
The directories are the newest:
999454 0755 2 root root 61440 Tue Mar 22 17:08:00 2011
/usr/share/man/man1
999456 0755 2 root root 110592 Tue Mar 22 17:08:00 2011
/usr/share/man/man3
999461 0755 2 root root 28672 Sat Mar 12 17:09:52 2011
/usr/share/man/man8
999460 0755 2 root root 12288 Fri Mar 11 17:44:05 2011
/usr/share/man/man7
999457 0755 2 root root 4096 Fri Mar 11 17:44:03 2011
/usr/share/man/man4
999458 0755 2 root root 12288 Fri Mar 11 17:43:57 2011
/usr/share/man/man5
999653 0755 2 root root 4096 Fri Mar 11 17:42:57 2011
/usr/share/man/fr/man8
1001029 0644 1 root root 1053 Fri Mar 4 14:01:19 2011
/usr/share/man/man1/avahi-publish-service.1.gz
, and it seems correct that ./man1 would be the youngest directory since
it has the youngest file (not including the directory, which is itself a
file).
The listing for the author's lst is on my response to Peter, so I won't
repeat that. What I continue to have trouble with is "breaking into"
perl controls. The more idiomatic the perl is, the less I can see how
control goes:
$ perl lst2
../text2
..
../find2.pl
../lst2
../text1
../find1.pl
../uvi
../symirror
../rmtree2
../rmtree1
../rename
../lst
../fdirs
../ch09.code
$ cat lst2
#!/usr/bin/perl
# lst - list sorted directory contents (depth first)
use Getopt::Std;
use File::Find;
use File::stat;
use User:
went;
use User::grent;
getopts('lusrcmi') or die <<DEATH;
Usage: $0 [-mucsril] [dirs ...]
or $0 -i [-mucsrl] < filelist
Input format:
-i read pathnames from stdin
Output format:
-l long listing
Sort on:
-m use mtime (modify time) [DEFAULT]
-u use atime (access time)
-c use ctime (inode change time)
-s use size for sorting
Ordering:
-r reverse sort
NB: You may only use select one sorting option at a time.
DEATH
unless ($opt_i || @ARGV) { @ARGV = ('.') }
if ($opt_c + $opt_u + $opt_s + $opt_m > 1) {
die "can only sort on one time or size";
}
$IDX = 'mtime';
$IDX = 'atime' if $opt_u;
$IDX = 'ctime' if $opt_c;
$IDX = 'size' if $opt_s;
$TIME_IDX = $opt_s ? 'mtime' : $IDX;
*name = *File::Find::name; # forcibly import that variable
# the $opt_i flag tricks wanted into taking
# its filenames from ARGV instead of being
# called from find.
if ($opt_i) {
*name = *_; # $name now alias for $_
while (<>) { chomp; &wanted; } # ok, not stdin really
} else {
find(\&wanted, @ARGV);
}
# sort the files by their cached times, youngest first
@skeys = sort { $time{$b} <=> $time{$a} } keys %time;
# but flip the order if -r was supplied on command line
@skeys = reverse @skeys if $opt_r;
my $counter = 0;
for (@skeys) {
unless ($opt_l) { # emulate ls -l, except for permissions
print "$_\n";
next;
}
$counter++;
print "counter is $counter\n";
if ($counter eq 1) {
print "$_ is the oldest file in this directory.\n";
}
$now = localtime $stat{$_}->$TIME_IDX();
printf "%6d %04o %6d %8s %8s %8d %s %s\n",
$stat{$_}->ino(),
$stat{$_}->mode() & 07777,
$stat{$_}->nlink(),
user($stat{$_}->uid()),
group($stat{$_}->gid()),
$stat{$_}->size(),
$now, $_;
}
# get stat info on the file, saving the desired
# sort criterion (mtime, atime, ctime, or size)
# in the %time hash indexed by filename.
# if they want a long list, we have to save the
# entire stat object in %stat. yes, this is a
# hash of objects
sub wanted {
my $sb = stat($_); # XXX: should be stat or lstat?
return unless $sb;
$time{$name} = $sb->$IDX(); # indirect method call
$stat{$name} = $sb if $opt_l;
}
# cache user number to name conversions
sub user {
my $uid = shift;
$user{$uid} = getpwuid($uid)->name || "#$uid"
unless defined $user{$uid};
return $user{$uid};
}
# cache group number to name conversions
sub group {
my $gid = shift;
$group{$gid} = getgrgid($gid)->name || "#$gid"
unless defined $group{$gid};
return $group{$gid};
}
$
Simple question: why does this script go off exactly like the original,
that is, why does program never print the value of $counter?