Sort All the Files in a Directory Tree
This function sorts all the files in a directory tree by modification time.
To obtain the modification time, the program calls the built-in function stat(). This returns a list with 13 elements. The modification time is the 10th element of this list; the rest can be ignored.
To display the modification time, the program calls localtime(), which returns a nine-element list. The program cares only about the first six, which are seconds, minutes, hours, day, month, and year, except the month is zero-based and the year has 1900 subtracted from it. (The other three elements in the list are day of the week, day of the year, and a flag for daylight savings time.)
The program prints the name of each directory in the tree as it processes it and then prints an ordered list of all the files with their modification time.
Directories are read using the functions opendir(), readdir(), and closedir(), which are fairly self-explanatory. A handle is passed to opendir() and then used as a parameter to readdir() and closedir(). Similar to reading data from file handles such as STDIN, calling readdir() in scalar context returns the name of a single file, while calling it in list context returns the entire file list. Just the filename is returned, with no path component.
Keep in mind that when you scan a directory, two special names are returned: "." and "..", which correspond to the current and parent directory. These two names should be ignored; otherwise, the program would loop forever and never terminate.
The program assumes that the path separator is a backslash, although this is simple to change.
Recall from an earlier program that Perl's built-in sort() function takes an optional subroutine as a parameter. This subroutine is passed the two elements to compare in the variables $a and $b, and should return -1, 0, or 1, depending on the appropriate ordering of the two elements. The <=> operator returns the same three values after doing a numeric comparison, so it is often used as it is in this line of the program:
@filelist =
sort { $filetimes{$a} <=> $filetimes{$b} } @filelist;
Also, recall from an earlier program that the x operator, as used in
" " x $depth
produces the number of spaces specified by $depth.
Perl has a series of tests that can be performed on a file, which are all specified by a hyphen and a letter followed by a filename or file handle. Among these tests are -f, which is true if the filename refers to a plain file, and -d, which returns true if the filename refers to a directory:
if (-f $fullname) {
# it's a plain file
} elsif (-d $fullname) {
# it's a directory
}
Some of the other tests (which aren't used here) return numeric values, such as -s, which returns the size of a file in bytes:
if (-f $file) {
print ("$file is a plain file\n");
$size = -s $file;
print ("Size is $size bytes\n");
}
Source Code
1. # this function processes a directory; for all
2. # subdirectories it calls itself recursively, and
3. # for all files it adds the filename to the
4. # @filenames directory and the file modification
5. # time to the %filetimes hash.
6. sub adddir {
7. # save these in locals since it recurses
8. my $depth = $_[0];
9. my $path = $_[1];
10. my($filename);
11. local(*DH); # my doesn't work for handles
12.
13. # show the directories as we process them
14. print ((" " x $depth) . "$path\n");
15.
16. # open the directory and scan each file in it
17. if (opendir DH, $path) {
18. foreach $filename (readdir DH) {
19.
20. # skip . and ..
21. unless (($filename eq ".") ||
22. ($filename eq "..")) {
23. my $fullname = "$path\\$filename";
24.
25. # it it a file?...
26. if (-f $fullname) {
27. push (@filelist, $fullname);
28. # store modification time in hash
29. $filetimes{$fullname} =
30. (stat $fullname)[9];
31. # ...or a directory?
32. } elsif (-d $fullname) {
33. adddir ($fullname, $depth+1);
34. }
35. }
36. }
37. }
38. closedir DH;
39. }
40.
41. # determine the start directory, default to current
42.
43. $startdir = $ARGV[0];
44. $startdir = "." unless defined($startdir);
45.
46. # this call does all the work recursively
47. adddir($startdir, 0);
48.
49. @filelist =
50. sort { $filetimes{$a} <=> $filetimes{$b} } @filelist;
51. foreach (@filelist) {
52. ( $sec, $min, $hour,
53. $day, $mon, $year,
54. undef, undef, undef ) = localtime $filetimes{$_};
55. printf("%02d/%02d/%4d %02d:%02d:%02d ",
56. $mon+1, $day, $year+1900, $hour, $min, $sec);
57.
58. # if the program was called with . as the argument
59. # (or default), then all paths will have .\ at the
60. # beginning, so remove that when printing.
61. if ($_ =~ /^\.\\/) {
62. print(substr($_, 2) . "\n");
63. }
64. else {
65. print("$_\n");
66. }
67. }
Suggestions
A hash can have only one value for a given key. Does the program ensure that it never accidentally replaces an element in %filetimes? Because the adddir() function calls itself recursively, does it ensure that it never calls itself recursively with the same parameters that were passed (which would lead to an infinite loop)? Does the initialization of $startdir on lines 43-44 properly handle the initialization of the variable to the default value (the current directory) if it was not provided as an argument? Check the code on lines 61-62 to ensure that it runs in the proper situations and performs the correct modification of the output string.
Hints
Walk through the program with a directory named abc, which contains the following:
Three files named d.txt, e.txt, and f.txt. Two files called jones and thompson, and a directory named smith, which contains two files named joe and john.
Explanation of the Bug
The initialization of the local variables that hold the parameters to adddir(), on lines 8-9
my $depth = $_[0];
my $path = $_[1];
has an F.init error. The first parameter is the path and the second is the depth, so the code should read as follows:
my $path = $_[0];
my $depth = $_[1];
The code as it reads now just won't work; since the initial depth is passed as 0, with the parameters to adddir() being swapped, the code tries to open a directory called "0", which probably doesn't exist.
|