http://qs1969.pair.com?node_id=1077221

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

I'm trying to print all the files and directories within a directory(including files and directories in subdirectories. this is my code;

use strict; use warnings; use Data::Dumper; my $dir = 'C:\Users\test\Dropbox\Perl'; print_rec($dir); sub print_rec { opendir DIR, shift; while( readdir(DIR) ) { if(!/\.|\.\./ && -d $_) { print "$_ is a directory\n"; return print_rec($_); } elsif (!-d) { print "$_ \n"; } } }

My program stops after the it reaches the first deepest level, actually it not returns at the previous level. What am i doing wrong. Understanding this will help me understand how the stack trace works in perl. Thanks !!

Replies are listed 'Best First'.
Re: Recursive Directory print
by karlgoethebier (Abbot) on Mar 06, 2014 at 13:37 UTC
    "I'm trying to print all the files and directories..."

    Someone else did it already for you:

    #!/usr/bin/env perl use strict; use warnings; use IO::All; use feature qw(say); my $dir = shift || q(.); my $io = io($dir); say for $io->all(0);

    See also IO::All and File::Find::Rule.

    Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

Re: Recursive Directory print
by Don Coyote (Hermit) on Mar 06, 2014 at 13:18 UTC

    readdir reads the directory listing. It does not supply the full path name. When you return a call to print_rec($_) you neither change the current working directory or supply the full path to the directory name being passed in $_.

    Assuming you run the program from your current working directory, your recursive calls attempt to open directories listed one directory below the directory you are calling the routine from, but in the directory you are calling it from instead of from the one below.

    sub print_rec { my $path = shift; opendir DIR, $path; while( readdir(DIR) ) { if(!/\.|\.\./ && -d $_) { print "$_ is a directory\n"; return print_rec($path.'\'.$_); } elsif (!-d) { print "$_ \n"; } } }
Re: Recursive Directory print
by zentara (Archbishop) on Mar 06, 2014 at 15:37 UTC
    File::Find works well for this too.
    #!/usr/bin/perl -w use File::Find; use strict; my $directory= "."; find(\&files, $directory); sub files { if( -d ){ print "Directory $File::Find::name\n"; }else { print " $File::Find::name\n"; } }

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh
Re: Recursive Directory print
by sn1987a (Deacon) on Mar 06, 2014 at 14:17 UTC

    In addition to the issue corrected by Don Coyote above, there are two addition problems:

    First, is the return print_rec($_). The return means you will stop processing as soon as you finish processing the first directory you encounter.

    Second,the handle DIR is global and the later opendirs will overwrite the early ones. You are better of using a lexical handle like my $dir.

    With those corrections the sub looks like:
    sub print_rec { my $path = shift; opendir my $dir, $path; while( readdir($dir) ) { if(!/\.|\.\./ && -d $_) { print "$_ is a directory\n"; print_rec($path.'\'.$_); } elsif (!-d) { print "$_ \n"; } } }

      The return statement is undoubtedly the crux of the OP’s problem:   as soon as the innermost occurrence returns, all of them do.   This program will probably work as intended if the word return is taken out.

Re: Recursive Directory print
by Discipulus (Canon) on Mar 06, 2014 at 12:46 UTC
    Perl is doing what you told him...
    #pseudocode while( readdir(DIR) ) { if is dot or dot-dot or is a directory print ' +Found'. else print.Stop.}
    No recusrion is involved or, well, implemented by you.
    This was one of my very earliest headcache in Perl: please read a correct recursive implemntation by tachyon here. Be sure to fully understand the code.
    This question is very very similar to an old one done by a legendary user

    Hth
    L*
    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

      please read a correct recursive implemntation by tachyon here.

      Hmm, I am sorry, but tachyon's solution to which you refer is most probably correct, but it is not recursive (a recursive algorithm implies that a subroutine calls itself, there is not even one function call in tachyon's solution.) Actually, even tachyon admits that it is not recursive. (Which, of course, doesn't mean in any way it is bad, it is just not recursive and therefore does not fit the OP's bill of requirements.)

        wow.. vey good point.. thanks Laurent_R.

        The central point, as many times, is a correct understanding of words and semantic they cover.
        This can be useful for the OP and for other as me that have missed this difference (from wikipedia):
          Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem
          Iteration is the repetition of a block of statements within a computer program
        That said obviously the tachyon solution falls in the iterative category..
        #excert from tachyon's code at http://www.perlmonks.org/?node_id=15785 +1 my @dirs = ($root); for my $path (@dirs){ opendir ( DIR, $path ) or next; # skip dirs we can't read while (my $file = readdir DIR) { next if $file eq '.' or $file eq '..'; # skip dot files if ( -d $path.$file ) { push @dirs, $path.$file.'/'; # add dir to list } } closedir DIR; }
        but..
        ..the logic of the snippet IS recursive in the way it populates @dirs: in fact it iterates over a list updated dynamically while processing the iteration itself. It produes a directory tree, or well a list of different depth objects. Also here the problem is divided in small pieces to process. But in the opposite way: @dirs starts populated by the root node only and while it is processed the stack @dirs itself it is updated: the way @dirs is populated by push seems recursive, in a broad sense.

        In other words, as wikipedia tell us some line below:

        Recursion and iteration are equally expressive: recursion can be replaced by iteration with an explicit stack, while iteration can be replaced with tail recursion.


        Hth
        L*
        There are no rules, there are no thumbs..
        Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Recursive Directory print
by Laurent_R (Canon) on Mar 06, 2014 at 18:44 UTC
    If you want to do the recursive search yourself, using the glob function (rather than opendir and readdir) makes it somewhat simpler because glob returns the relative path of the file together with the file. The following program prints only the files, but searches the directories and subdirectories:
    use strict; use warnings; search_dir (shift); sub search_dir { my $path = shift; my @dir_entries = glob("$path/*"); foreach my $entry (@dir_entries) { print $entry, "\n" if -f $entry; search_dir($entry) if -d $entry; } }
    If you want to print also the directories, you might change the relevant lines to this:
    print "$entry is a file \n" if -f $entry; print "$entry is a dir \n" and search_dir($entry) if -d $entry +;
Re: Recursive Directory print
by kcott (Archbishop) on Mar 07, 2014 at 08:50 UTC

    G'day zavo,

    Welcome to the monastery.

    "What am i doing wrong."

    Quite a lot actually. Let's step through it. [A number of the issues have already been raised; I've flagged these with "issue already addressed".]

    sub print_rec {

    Firstly, print_rec is not a meaningful name. In a tiny script like this, you can see at a glance what the routine is supposed to be doing. In a more complex script, where sub_name() may be some screenfuls away from the sub sub_name {...} definition, this won't be at all obvious: I would probably assume that print_rec() was printing records, not directory listings.

    You're passing an argument to that routine. As you'll see, you're going to need to use that argument more than once: assign it to a variable. [issue already addressed]

    opendir DIR, shift;

    Change shift to the variable (from previous point).

    DIR is a package global variable; use a lexical variable. [issue already addressed]

    You're not checking if this worked. See readdir for example code; alternatively, use the autodie pragma (my preference).

    if(!/\.|\.\./ && -d $_)

    The regex /\.|\.\./ matches anything with a single dot. The alternation (|\.\.) is completely pointless: if you've matched a single dot, then two dots must also match. Assuming you want to match the current and parent directories (i.e. '.' and '..'), then you'll need to anchor the pattern to the start and end of the string: /^(?:\.|\.\.)$/

    -d $_ will check whether $_ is a directory in the current directory! You'll need to prefix this with the path to this filename.

    return print_rec($_);

    return shouldn't be here. [issue already addressed]

    $_ needs a path. [issue already addressed]

    elsif (!-d) { ... }

    I'd just change that to else { ... } — the if is catching directories so everything else must be not a directory.

    Finally, your listing should be presented in such a way that the directory structure can be easily seen. What you currently have is an unformatted list. Indentation, in much the same way as you do for your code, is a simple way to achieve this.

    Putting all that together, I came up with this (heavily) modified version of your code:

    #!/usr/bin/env perl -l use strict; use warnings; use autodie; use constant INDENT => ' ' x 4; my $starting_dir = '.'; print_dir_listing($starting_dir, ''); sub print_dir_listing { my ($path, $indent) = @_; opendir my $dh, $path; for (readdir $dh) { next if /^(?:\.|\.\.)$/; if (-d "$path/$_") { print "${indent}DIR: $_"; print_dir_listing("$path/$_", $indent . INDENT); } else { print "$indent$_"; } } }

    In the directory from where I ran this, the listing was 2,752 lines long. Here's an extract to show what the output looks like.

    ... DIR: mastering_perltk_demo_code mastperltk_examples.MANIFEST mastperltk_examples.tar.gz DIR: mptk-code14 DIR: ch01 hello-world DIR: ch02 play-with-pack1 play-with-pack2 DIR: ch03 banner fontviewer DIR: ch04 ...

    -- Ken

Re: Recursive Directory print
by Lennotoecom (Pilgrim) on Mar 06, 2014 at 18:47 UTC
    There are a couple more methods
    if windows:
    $dir = "D:\books"; @output = `dir $dir /B /S`;
    if unix:
    $dir = "/some/path"; @output = `find $dir`;
Re: Recursive Directory print
by zavo (Initiate) on Mar 06, 2014 at 13:07 UTC

    here is my logic and explain me where i do wrong. I open the root directory. If i find another directory i print it and the call the print_rec subroutine for that directory, and i do this until i run out of dirs or files. What i don't get is how or where the print_rec function is returning after terminates execution for some particular directory