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

Im not sure why its not searching in the sub dir. Im trying to find all files with .sas extension and change the permissions for each .sas files. Thanks in advance for your help. Here is the code:
&recurse_dir("/cdw/home_dir/s006258/CSPAM"); sub recurse_dir { my ($dir) = @_; opendir(DIR, "$dir") or die "Unable to open $workdir:$!\n"; my @names = readdir(DIR) or die "Unable to read $workdir:$!\n"; foreach my $name (@names) { if (-d $name){ print "$name is a dir\n"; if ($name eq "." || $name eq ".." || $name eq "lost") +{ next; } else{ recurse_dir($name); } } else { if (-f $name){ #print "$name is a file\n"; if ($name =~ /\.sas/) { system("chmod 644 $name"); print "$name has been changed\n"; } } } } }

Replies are listed 'Best First'.
Re: recursive dir and file
by xdg (Monsignor) on Nov 11, 2005 at 14:49 UTC

    When you're recursing, you're passing the name of the subdirectory, but not the full path to it. You either need to concatenate it to the original directory path, or you need to chdir into and out of the subdirectory.

    A better alternative may be to use File::Find or File::Find::Rule if those are available to you. Let them manage your recursion and spend your energy writing the code for what you actually want to do with each file.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: recursive dir and file
by Tanktalus (Canon) on Nov 11, 2005 at 14:52 UTC

    First - you're reusing a global variable DIR. Either localise it before the opendir, or closedir it before the foreach.

    Second, the problem with readdir is that you've lost your context. You probably need to do a chdir before you opendir so that when you're looking at $name, you'll be looking at it relative to the requested directory. That's the easy way, although it changes your current working directory for the rest of the script. And you'll need to chdir back after the recursive call to recurse_dir($name).

    Or, you could just use File::Find and get all this done for you. Way easier. ;-)

    sub recurse_dir { require File::Find; File::Find::find(sub { if (-f $File::Find::name and $File::Find::name =~ /\.sas$/) { chmod 0644, $File::Find::name; print "$File::Find::name has been changed\n"; } }, @_); }

    (I also took the liberty of achoring your .sas test, and of using perl's built-in chmod function.)

Re: recursive dir and file
by tirwhan (Abbot) on Nov 11, 2005 at 15:00 UTC

    opendir only opens the directory for you, it does not change your current working directory. So you either need to do a chdir before doing any more tests on the files therein or prepend the directory name to the path.

    Another possibility would be to use File::Find:

    use File::Find; find (\&change_sas,"/cdw/home_dir/s006258/CSPAM"); sub change_sas { my $filename=$_; if (-f $filename && $filename=~m/\.sas\z/) { chmod 0644,$filename; print "Found $filename, changing\n"; } }

    Or the *NIX find utility.

    find /cdw/home_dir/s006258/CSPAM -name '*.sas' -exec chmod 644 {} \;

    Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. -- Brian W. Kernighan

      -exec should be avoided. It spawns one process per matched file. In almost every case, you want find -print0 | xargs -r0 instead, which will spawn only one process for as many files as will fit on its command line – usually, that means just one process.

      find /cdw/home_dir/s006258/CSPAM -name '*.sas' -print0 | xargs -r0 chm +od 644

      Makeshifts last the longest.

Re: recursive dir and file
by blazar (Canon) on Nov 11, 2005 at 15:03 UTC
      Problem is Im running this script on the Unix server and I don't have permission to install any modules. This is the only way I can think of.... Any other solution?

        File::Find is a core module going back to Perl 5.0. If you're running any version of Perl 5, then you have it.

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        I really like File::Finder for doing things like this.

        As for not being able to install modules you can usually install the module source in your own directory. To install File::Finder you could do the following.

        Create a directory named File in the same directory as your script is in.

        Download the Finder.pm file from the CPAN link above. (There is a link named "Source" near the top of the page that explains the module.) Save this file in the File directory

        At this point you should have a file tree something like:
        /home/work/myscript.pl /home/work/File/Finder.pm

        Now when you run your script it should look in the current directory for the modules in addition to the system directories.
        If you're doing this on a unix machine, you don't need perl at all. While File::Find is part of the Perl "core", it is noticeably slower than the unix-native "find" utility. All you need is a unix command line like this:
        find /cdw/home_dir/s006258/CSPAM -name '*.sas' -print0 | xargs -0 chmo +d 644
        Note the single-quotes around the '*.sas', to keep the asterisk from being "interpreted" by the shell.

        Perl is great, but for little things like this, the shell can be better.

Re: recursive dir and file
by aquarium (Curate) on Nov 11, 2005 at 15:02 UTC
    you're passing to the recursive function a scalar, but in the function you treat it as a list (@_) instead of a simple (shift). assigning a list in scalar context assigns the count of the list to the variable instead of the value of the first list element.
    the hardest line to type correctly is: stty erase ^H