Getting names of directories under given path
Asked Answered
A

9

4

I am trying to get the names of all first level directories under given path.

I tried to use File::Find but had problems.

Can someone help me with that?

Asyndeton answered 7/11, 2009 at 9:3 Comment(0)
S
25

Use the-d file check operator:

#!/usr/bin/perl

use strict;
use warnings;
use autodie;

my $path = $ARGV[0];
die "Please specify which directory to search" 
    unless -d $path;

opendir( my $DIR, $path );
while ( my $entry = readdir $DIR ) {
    next unless -d $path . '/' . $entry;
    next if $entry eq '.' or $entry eq '..';
    print "Found directory $entry\n";
}
closedir $DIR;
Siliqua answered 7/11, 2009 at 10:6 Comment(14)
it gives me only . , .. directories . Not getting the other directories stored there .Asyndeton
Sinan: Who? Me? I don't see where the problem is.Siliqua
Ah! He's one of the guys that use spaces before their punctuation. I really thought he meant "give path '.'". I'll change my answer accordinglySiliqua
Thanks for the upvote. I know that you were trying to address the OP's confusion. But somehow that got me confused ;-)Siliqua
Should check return values of opendir and closedir.Reichert
And that's what why you voted this down? What the hell is wrong today?Siliqua
OK. So I edited this to check the return value of opendir. I refuse to check closedir's return value though, unless somebody tells me what the script should do should closedir fail.Siliqua
I hadn't noticed. I think autodie is nice, but I also think that it's too much magic for noobs.Siliqua
@Manni FYI: I cleaned up my comments.Bagpipes
You need to check the return value of readdir() , otherwise it will break if there is a file named 0 . (Although this should be fixed for Perl 5.11.2 )Hadleigh
Yes, it'll probably miss the directory named "0". Could I please have the next downvote now because I didn't post the unit tests? And what if someone pulls the hard drive's plug while the script is running?Siliqua
@Manni: atomic changes or bust. Is is me, or is the [perl] tag getting a bit dogmatic lately? (Seems like a misplaced overcorrection to the general SO opinion that Perl is bad because of TMTOWTDI.)Dolph
Discouraging stuff that gives Perl a bad name is one thing, but being overly pedantic is a completely different thing. I should have only posted that first sentence.Siliqua
10 lines of code when 1 will do ? What is wrong with you ? Are you OK? You need to talk to somebody manBadgett
G
8

If you don't need to traverse the entire directory hierarchy, File::Slurp is much easier to use than File::Find.

use strict;
use warnings;
use File::Slurp qw( read_dir );
use File::Spec::Functions qw( catfile );

my $path = shift @ARGV;
my @sub_dirs = grep { -d } map { catfile $path, $_ } read_dir $path;
print $_, "\n" for @sub_dirs;

And if you ever do need to traverse a hierarchy, check CPAN for friendlier alternatives to File::Find.

Finally, in the spirit of TIMTOWTDI, here's something quick and sleazy:

my @sub_dirs = grep {-d} glob("$ARGV[0]/*");
Grimalkin answered 7/11, 2009 at 11:2 Comment(1)
You can do read_dir $path, prefix => 1 instead of map { catfile $path, $_ } read_dir $pathClinandrium
A
3
use File::Spec::Functions qw( catfile );

my ($path) = @ARGV;

opendir my $DIR, $path 
    or die "Cannot open directory: '$path': $!";

while ( my $entry = readdir $DIR ) {
    next if $entry =~ /\A\.\.?\z/;
    next unless -d catfile $path, $entry;
    print $entry, "\n";
}

closedir $DIR;

This worked for me.

Asyndeton answered 7/11, 2009 at 10:32 Comment(5)
Are you asking about directories under '.' like you question implies or are you asking about subdirectories of a directory that is given as a command line argument?Siliqua
subdirectories of a directory that is given as a command line argumentAsyndeton
I changed my answer accordingly.Siliqua
can you explain this regex plz /\A\.\.?\z/Asyndeton
You need to check the return value of readdir() , otherwise it will break if there is a file named 0 . ( Although this should be fixed for Perl 5.11.2 )Hadleigh
G
1

I'm running ActivePerl 5.10.1 under Windows XP. If I wanted to get all the names of the directories under the root drive F. I would use the following code:

#!perl
opendir (DIR,'F:/');
my @folder = readdir(DIR);
foreach my $f (@folder)
{
   next if ($f =~ /\./);
   print "$f\n";
 }

Well, this usually works because my folder names do not contain the dot. Otherwise it fails.

Okay, it seems that even my method works for my case, people would still downvote because it is faulty. So I'd have to use the official approach, the -d flag to check if a file is a directory:

The upgraded code:

#!perl
use strict;
use warnings;

opendir (DIR, "F:/");
my @files = readdir(DIR);
my @dirs = grep { -d } @files;
print @dirs;
Gentille answered 7/11, 2009 at 9:34 Comment(3)
I have a file that called "makefile" with no . extension and it was included in your check .Asyndeton
My method is faulty. It only works if the file names contain the dot extension and the folder names do not contain the dot. Looks like Manni's solution works much better for your case.Gentille
This does not work. @files only contains the name (not the full path!) of each entry in the directory. So using -d on them only gives you '.' and '..' on Windows.Frohne
S
0

Use opendir and -d.

Shippy answered 7/11, 2009 at 9:13 Comment(1)
I should have read my post before pressing 'add comment'. I meant to write -dUnderexpose
P
0

you can use find2perl to translate your find command to perl. See perldoc find2perl for more info.

Workaround of maxdepth: (reference from Randall)

$  find2perl /home/path -type d -eval 'my $slashes = $File::Find::name =~ tr#/##;return $File::Find::prune = 1 if $slashes > 2;return if $slashes ==2'

Code:

   use strict;
    use File::Find ();
    use vars qw/*name *dir *prune/;
    *name   = *File::Find::name;
    *dir    = *File::Find::dir;
    *prune  = *File::Find::prune;
    sub wanted;

    File::Find::find({wanted => \&wanted}, '/');
    exit;
    sub wanted {
        eval { my $slashes = $File::Find::name =~ tr#/##;return $File::Find::prune = 1 if $slashes > 1;return if $slashes ==1 };
        if ( $? == "0" && -d _  ){
            print "$name --- \n";   
        }
    }

output

$ pwd
/temp

$ tree
.
|-- dir1
|   |-- subdir1
|   |   |-- subsubdir1
|   |   `-- testsubdir1.txt
|   `-- testdir1.txt
|-- dir2
|   |-- subdir2
|   |   |-- subsubdir2
|   |   `-- testsubdir2.txt
|   `-- testdir2.txt
|-- dir3
|   `-- subdir3
|       `-- subsubdir3
`-- test

9 directories, 5 files

$ perl perl.pl
/temp ---
/temp/dir3 ---
/temp/dir1 ---
/temp/dir2 ---
Profanatory answered 7/11, 2009 at 9:38 Comment(9)
I did not vote your answer down, however: find2perl C:/ -maxdepth 1 gives Unrecognized switch: -maxdepthBagpipes
What do you mean "if you dare"? Are you going to somehow punish him for explaining his reasons?Siliqua
@manni, no. just a psychological challenge. Because this site doesn't require a down voter compulsory comment.Profanatory
@Profanatory Seriously ... Did you actually look at the generated code or try running it under directory with a large tree under it. It has been going through my C:\Windows for about a minute now. I would say readdir and/or File::Slurp::read_dir are perfectly fine and there is really no need for File::Find here.Bagpipes
@sinan, of course i did...i tested under linux. see my results. why don't you try running it on linux before you jump to conclusion?Profanatory
Also i would say File::Find is perfectly fine too (and i didn't say we shouldn't use File::Slurp either)... TIMTOWTDI in Perl, remember?Profanatory
Yes, there's more than one way to do it. But not each possible way can be the best (or even good).Siliqua
the advantage of Find is if i want to go another level deep or more levels deep, its easy to change the parameter.Profanatory
Seriously, the reason for down voting should be if the answer is way out of solving the problem. Using File::Find is perfectly fine. It still can solve the problem. Further, OP is trying to use File::Find, and i showed him how it can be done. WTH is wrong?Profanatory
S
0

Using File::Find::Rule

#!/usr/bin/perl --
use strict;
use warnings;

use Shell::Command qw( rm_rf touch mkpath );
use autodie;
use File::Find::Rule;

Main(@ARGV);
exit(0);

sub Main{
  use autodie;
  my $dir = "tmp";
  mkdir $dir;
#~   chdir $dir;
  mkpath "$dir/a/b/c/d";
  mkpath "$dir/as/b/c/d";
  mkpath "$dir/ar/b/c/d";

  print `tree`;
  print "---\n";
  print "$_\n"
    for File::Find::Rule->new->maxdepth(1)->directory->in($dir);

  print "---\n";

  print "$_\n"
    for grep -d, glob "$dir/*"; ## use forward slashes, See File::Glob

#~   chdir "..";
  rm_rf $dir;
}
__END__
.
|-- test.pl
`-- tmp
    |-- a
    |   `-- b
    |       `-- c
    |           `-- d
    |-- ar
    |   `-- b
    |       `-- c
    |           `-- d
    `-- as
        `-- b
            `-- c
                `-- d

13 directories, 1 file
---
tmp
tmp/a
tmp/ar
tmp/as
---
tmp/a
tmp/ar
tmp/as

Or using File::Find::Rule frontend findrule

$ findrule tmp -maxdepth ( 1 ) -directory
tmp
tmp/a
tmp/ar
tmp/as
Shaw answered 8/11, 2009 at 19:57 Comment(0)
B
0
 $path="/path/to/search";

 @files = `find $path -name myfile.name -type f`;
 print "@files\n";
Badgett answered 13/2, 2023 at 15:8 Comment(0)
K
-1

You could use File::Find for that. For example:

use File::Find ();

File::Find::find(\&wanted, '.');

sub wanted {
    if (-d) {    
        print "$File::Find::name\n";
    }
}

For each file found under '.', this will call the wanted subroutine. Inside the subroutine you can use -d to check for a directory.

File::Find:find descends to all subdirectories in the tree below the directory specified.

Kalong answered 7/11, 2009 at 11:12 Comment(2)
Sorry, this is not a good substitute for readdir or File::Slurp::read_dir.Bagpipes
Can you explain that? I personally think read_dir is not a good substitute for this ;)Kalong

© 2022 - 2024 — McMap. All rights reserved.