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?

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
Night Walker
  • 20,638
  • 52
  • 151
  • 228

9 Answers9

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;
innaM
  • 47,505
  • 4
  • 67
  • 87
  • it gives me only . , .. directories . Not getting the other directories stored there . – Night Walker Nov 07 '09 at 10:12
  • Sinan: Who? Me? I don't see where the problem is. – innaM Nov 07 '09 at 11:05
  • 1
    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 accordingly – innaM Nov 07 '09 at 11:28
  • Thanks for the upvote. I know that you were trying to address the OP's confusion. But somehow that got me confused ;-) – innaM Nov 07 '09 at 11:33
  • 1
    Should check return values of `opendir` and `closedir`. –  Nov 07 '09 at 11:36
  • 2
    And that's what why you voted this down? What the hell is wrong today? – innaM Nov 07 '09 at 11:39
  • 3
    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. – innaM Nov 07 '09 at 12:15
  • I hadn't noticed. I think autodie is nice, but I also think that it's too much magic for noobs. – innaM Nov 07 '09 at 13:02
  • @Manni FYI: I cleaned up my comments. – Sinan Ünür Nov 07 '09 at 13:33
  • 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` ) – Brad Gilbert Nov 07 '09 at 15:03
  • 6
    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? – innaM Nov 07 '09 at 16:56
  • @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.) – Telemachus Nov 07 '09 at 18:48
  • 1
    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. – innaM Nov 07 '09 at 18:58
  • 10 lines of code when 1 will do ? What is wrong with you ? Are you OK? You need to talk to somebody man – niken Feb 13 '23 at 15:11
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]/*");
FMc
  • 41,963
  • 13
  • 79
  • 132
  • You can do `read_dir $path, prefix => 1` instead of `map { catfile $path, $_ } read_dir $path` – Rami Jul 27 '22 at 06:07
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.

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
Night Walker
  • 20,638
  • 52
  • 151
  • 228
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;
Mike
  • 1,841
  • 5
  • 24
  • 34
  • I have a file that called "makefile" with no . extension and it was included in your check . – Night Walker Nov 07 '09 at 09:41
  • 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. – Mike Nov 07 '09 at 10:08
  • 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. – BrandonSun May 25 '17 at 07:49
0

Use opendir and -d.

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
René Nyffenegger
  • 39,402
  • 33
  • 158
  • 293
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 ---
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • I did not vote your answer down, however: `find2perl C:/ -maxdepth 1` gives `Unrecognized switch: -maxdepth` – Sinan Ünür Nov 07 '09 at 11:36
  • What do you mean "if you dare"? Are you going to somehow punish him for explaining his reasons? – innaM Nov 07 '09 at 11:37
  • @manni, no. just a psychological challenge. Because this site doesn't require a down voter compulsory comment. – ghostdog74 Nov 07 '09 at 12:48
  • 1
    @ghostdog74 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. – Sinan Ünür Nov 07 '09 at 13:40
  • @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? – ghostdog74 Nov 07 '09 at 14:04
  • 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? – ghostdog74 Nov 07 '09 at 14:12
  • 1
    Yes, there's more than one way to do it. But not each possible way can be the best (or even good). – innaM Nov 07 '09 at 14:27
  • the advantage of Find is if i want to go another level deep or more levels deep, its easy to change the parameter. – ghostdog74 Nov 07 '09 at 14:42
  • 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? – ghostdog74 Nov 07 '09 at 14:46
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
yo.
  • 11
  • 1
0
 $path="/path/to/search";

 @files = `find $path -name myfile.name -type f`;
 print "@files\n";
toolic
  • 57,801
  • 17
  • 75
  • 117
niken
  • 2,499
  • 3
  • 33
  • 56
-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.

Andomar
  • 232,371
  • 49
  • 380
  • 404