Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I'd like to be able to recurse a directory and generate a list of it's contents (and the contents of each subdir). The end goal is that I can create an HTML list sort of like this:

Any thoughts on the best way to do this? If it's easiest to add the ul and li HTML tags as we go that's fine, or if there's some wat to return a list that I can figure out how to add the ul/li tags to later that's fine as well.

Replies are listed 'Best First'.
Re: Builing a Recursive Directory Listing
by Roger (Parson) on Dec 15, 2003 at 12:16 UTC
    You could use the CPAN CORE modules File::Find, File::Spec::Functions, etc., to traverse the directory and capture the path and filenames. With a script somewhat like below...

    use strict; use warnings; use Data::Dumper; use File::Find; use File::Spec::Functions qw/ splitdir /; my %files; find ( { wanted => \&process }, "P:/vb.XpVisualStyle" ); print Dumper(\%files); sub process { # $File::Find::dir = /some/path/ # $_ = foo.ext # $File::Find::name = /some/path/foo.ext return if -d; # ignore directories, we are only interested in file +s my $fname = $_; my @dirs = splitdir($File::Find::dir); # build hash table or HTML output here # .... # build hash table %files->{path}{path} = [@filelist] my $src = "push \@{\$files{'" . join("'}{'", @dirs) . "'}}, '$fname' +"; eval "$src"; }
    And the resultant hash table is quite easy to convert into HTML ... you may have to tweak a bit.
    $VAR1 = { 'P:' => { 'vb.XpVisualStyle' => [ 'makeres.bat', 'xp_manifest.rc', 'xp_manifest.res', 'xp_manifest.xml', 'xp_visual.exe.manifest', 'xp_visual.txt' ] } };
    Update: changed CPAN to CORE for technical correctness.

Re: Builing a Recursive Directory Listing
by exussum0 (Vicar) on Dec 15, 2003 at 12:50 UTC
    Here's the NON CPAN-module-using version. I had a highschool project waaay back when that required a similar thing (finding file dupes with crc's). It's simple and a quick one with a few inline critiques.. but you get the idea :)

    use strict; use warnings; my $level = 0; processDir("."); ### # Possibly add a callback so that it doesn't only print # directories but can do other stuff too. ### sub processDir { my ($curDir) = @_; my $dirHandle = undef; opendir($dirHandle, "$curDir"); ### # Didn't feel like playing w/ regexps for a quick solution ### my @contents = sort grep { $_ ne "." and $_ ne ".." } readdir($dir +Handle); closedir($dirHandle); foreach my $file (@contents) { print "\t" x $level; print "$file"; if( -d "$curDir/$file" ) { print "/\n"; ### # Dont' follow symlinks... they cause loops ### if( ! ( -l "$curDir/$file" ) ) { ### # I should pass the level along or make it all # iterative. But you get the idea ### $level++; processDir( "$curDir/$file" ); $level--; } } else { print "\n"; } } }

    p.s. use cpan modules, they are proven. this is just to show you what you asked for in native code. I wouldn't be surprised if the Find::* utils didn't do something similiar w/o recursion.. but that wasn't the point :)

    Update: Grinder thought it didn't check symlinks when I was. Added a comment where the check is, + fix the -l test to actually work. On my machine, opendir() of a symlink doesn't work, but I won't say it won't on another machine. :)


    Play that funky music white boy..

      This approach is frowned upon in the general case, because if you follow symbolic links. If a chain of symbolic links ever gets back to above where you are in the the directory tree, you will have created a loop, and your program will never terminate.

      update: Duh! I skimmed too much and missed the -l test. Sorry sporty, bad me. I guess after having seen half a dozen wrong solutions posted here over the years one starts to expect them to be all wrong. Which by extension is another reason to use File::Find, it offers a much higher level of conceptual chunking.

      It might be something as simple as foo -> ../../../ but it might also be something more subtle: foo -> /bar/new/foo and something in /bar/new/foo points back here again. In my experience it's always1 been something fairly obscure, always involving new versions of applications and new disks being added to the machine. After a few years, this sort of cruft builds up and makes the scenario inevitable.

      The File::Find modules take care this problem into account, and can thereby save you from shooting yourself in the foot.

      Maybe I should point out that I'm not pointing the bone at sporty, but rather warning those who'll see this in the future and think that this would be a better approach than to use standard modules, NIH syndrome and all that.


      1. Always... well, this did happen to me once, a long time ago. It was a lesson learnt, and now I use File::Find.
        Actually, that's why the ! ( -l $file ) test is there.. so it doesn't. I should change this to $curDir/$file, which i'll update it to be :)

        I suggest you reread the code i posted. :) The test was there, it just wasn't written 'well'

        Update: I thought I said using the CPAN module was preferred and stuff. Guess I dreamed that :)


        Play that funky music white boy..

      I haven't looked through your code, but I just wanted to comment on your, er, comment about being "NON CPAN-module-using". You might note that File::Find is a core module (you can find the list of these in your perlmodlib perldoc). CPAN is not required to get it -- it is almost certainly included with your copy of perl itself. This is usually the thing to use, unless there is something very specific that this code does that File::Find does not. (Again, there might be, as I haven't looked at your code. But I'd be shocked if it couldn't be done with File::Find.)

      Please don't take this as a personal attack against you or your code. I noticed File::Find mentioned as a "CPAN module" in another note as well, so I figured the matter might use some clarification.

        You are 100% right about being able to do it w/ file find. I didn't know it was NOT cpan. Tnx for the clarification.

        I hope no one would use my code :) Being the CS person that I am, I like to explain what is going on that assuming that everyone else already doesn't. It's one of my favourite things... programemrs who think they know how things work, but really don't :)


        Play that funky music white boy..
Re: Builing a Recursive Directory Listing
by zentara (Cardinal) on Dec 15, 2003 at 17:10 UTC
    You could check out File-Slurp-Tree

    Or if you want a shell option, I like this:

    find . -print | sed -e 's,[^/]*/\([^/]*\)$,`--\1,' -e 's,[^/]*/,| ,g'