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

I have a few sub-folders in the main folder. My program will do some calculations in each sub-folder. Firstly the code will create the "result" folder in main folder for all calculations. And, for the calculation in each sub-folder I want to create a folder in the "result" folder. But they should have the same name as sub-folder.

My working directory is "/home/abc/Desktop/test". The "test" is my main folder. There are "a", "b" and "c" sub-folders in "test" folder. My code creates the "result" folder in "test" main folder. But it also should create "a", "b" and "c" sub-folders in "result" folder. How can I fix my code?
#!/usr/bin/env perl use strict; use warnings; use File::Path qw/make_path/; use Cwd; my $dir = cwd(); opendir (DIR, $dir) or die "Unable to open current directory! $!\n"; my @subdirs = readdir (DIR) or die "Unable to read directory! $!\n"; closedir DIR; my $result_path = "$dir/results"; make_path("$result_path"); foreach my $subdir ( sort @subdirs ) { chdir($subdir) or die "Cannot cd to $dir: $!\n"; make_path("$result_path/$subdir"); system("echo '1 0' | program -f methane.trr -o $result_path/$subdir/ou +tfile.txt"); chdir(".."); }

Replies are listed 'Best First'.
Re: How to get sub-folder names using Perl?
by Athanasius (Archbishop) on Mar 08, 2015 at 04:58 UTC

    Hello erhan,

    There are two problems with your code. The first is that it fails to distinguish between directories and files within the foreach loop. I assume your script resides in /home/abc/Desktop/test, in which case the call to readdir will return the name of the script (and the names of any other files in this directory) along with the subdirectory names. Then within the loop the call to chdir($subdir) will fail. As james28909 has shown, this is easily fixed by putting:

    next if -f $subdir;

    as the first statement within the loop, although I would prefer:

    next unless -d $subdir;

    The second problem is that the statement chdir(".."); at the end of the loop fails to return the current working directory to the correct location, so on a subsequent iteration chdir($subdir) is called with the value a, but there is no directory named a within the current working directory. A simple solution is to reset the current working directory at the start of each loop iteration:

    foreach my $subdir ( sort @subdirs ) { chdir($dir); next unless -d $subdir; ...

    Here is my preferred version:

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      @Athanasius, thanks for your reply. Your code works. Sorry. But I didn't understand its some parts:
      my $result_path = $dir . '/results';
      where, why did you use point . there?
      next unless -d
      Also I didn't understand what this part does.
      chdir $dir . '/' . $subdir;
      And this . '/' . part? Thanks in advance,

        Hello erhan,

        (1) In Perl there are two ways to concatenate strings:

        1. The dot operator: see perlop#Additive-Operators
        2. Double-quote interpolation: see perlop#Quote-and-Quote-like-Operators

        In the present case, my $result_path = $dir . '/results'; has the same effect as my $result_path = "$dir/results";, so the choice between them is merely stylistic. But as a general rule, it’s best to avoid double-quote interpolation if you don’t need it: see Whats-wrong-with-always-quoting-vars of perlfaq4.

        (2) The line next unless -d $subdir; causes the foreach loop to skip to the next iteration (i.e., to ignore the current value of $subdir) unless the current $subdir is a directory (and not a file, a symbolic link, etc.). It uses the -d function which is documented in functions/-X.

        (3) The line chdir $dir . '/' . $subdir; contains two more concatenation operators, and is equivalent to:

        chdir "$dir/$subdir";

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: How to get sub-folder names using perl?
by james28909 (Deacon) on Mar 08, 2015 at 01:53 UTC
    use strict; use warnings; use File::Path qw( make_path ); use File::Slurp; use Cwd; my $cwd = getcwd(); my @dirs = read_dir($cwd); for(@dirs){ next unless -d $_; make_path( "$cwd/result/$_" ); open (my $file, '>', "$cwd/result/$_/test file for $_.txt") or die + "$!"; print $file "this is a test for directory $_"; #or print calculati +on to $file }
    But i am having a little bit of a time trying to understand the question. This will only create folders in "result" directory if there are folders in "test" directory. The actual file handling wouldnt be much more trivial than this ;)

    EDIT: Updated the code to reflect advice described below

      @james28909, thanks. It works. But I didn't understand this line: next if -f $_;

      Can you please explain that line? Why did you use -f and $_? Thanks again.
        You said that you want to work on subfolders. The
        next if -f $_;
        checks if the current directory entry (contained in $_) is a regular file (as opposed to directories or folders in MS parlance), and, if it is, goes directly to the next directory entry in the list. This way, the script will only work on subdirectories.

        Update: as correctly pointed out below by Happy-the-monk, the code above will filter out only plain files (I used the words "regular files" to mean that). So that it is not completely accurate to write, as I did above, that the script will only work on subdirectories, since there can be other entities than just plain files and subdirectories. And it would be better to identify real directories with something like next unless -d $_;. Thank you, Happy-the-monk, for the added accuracy.

        Je suis Charlie.
Re: How to get sub-folder names using perl?
by zwon (Abbot) on Mar 08, 2015 at 22:22 UTC
    Nowadays, if I have to do anything with files I use Path::Tiny, it makes code much cleaner. Here's how your script would look with it:
    #!/usr/bin/env perl use strict; use warnings; use Path::Tiny; my $dir = Path::Tiny->cwd; my @subdirs = grep { $_->is_dir } $dir->children; my $results = $dir->child('results'); for my $subdir (@subdirs) { my $ressubdir = $results->child($subdir->basename); $ressubdir->mkpath; chdir $subdir; system("pwd > $ressubdir/outfile.txt"); } chdir $dir;