tags:

views:

239

answers:

8

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?

A: 

Use opendir and -d.

René Nyffenegger
what do you mean by -f ? http://perldoc.perl.org/functions/opendir.html
Night Walker
I should have read my post before pressing 'add comment'. I meant to write -d
René Nyffenegger
A: 

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
I have a file that called "makefile" with no . extension and it was included in your check .
Night Walker
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
+1 For trying to be helpful :)
Andomar
A: 

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
to the down-voter, explain why you down vote, if you dare.
ghostdog74
I did not vote your answer down, however: `find2perl C:/ -maxdepth 1` gives `Unrecognized switch: -maxdepth`
Sinan Ünür
What do you mean "if you dare"? Are you going to somehow punish him for explaining his reasons?
innaM
@manni, no. just a psychological challenge. Because this site doesn't require a down voter compulsory comment.
ghostdog74
@sinan, maxdepth is not valid, but there is workaround.
ghostdog74
@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
@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
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
Yes, there's more than one way to do it. But not each possible way can be the best (or even good).
innaM
the advantage of Find is if i want to go another level deep or more levels deep, its easy to change the parameter.
ghostdog74
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
+8  A: 

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
it gives me only . , .. directories . Not getting the other directories stored there .
Night Walker
Sinan: Who? Me? I don't see where the problem is.
innaM
As an example, it's good enough, isn't it?
Nathan Fellman
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
Thanks for the upvote. I know that you were trying to address the OP's confusion. But somehow that got me confused ;-)
innaM
Should check return values of `opendir` and `closedir`.
Kinopiko
And that's what why you voted this down? What the hell is wrong today?
innaM
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
I hadn't noticed. I think autodie is nice, but I also think that it's too much magic for noobs.
innaM
@Manni FYI: I cleaned up my comments.
Sinan Ünür
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
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
@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
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
+2  A: 
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.

Night Walker
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?
innaM
subdirectories of a directory that is given as a command line argument
Night Walker
I changed my answer accordingly.
innaM
can you explain this regex plz /\A\.\.?\z/
Night Walker
`\A` matches the beginning of the string, `\.` matches `.` and `\.?` matches zero or one `.` `\z` matches the end of the string (`$` matches end of string or end of line so can be problematic with filenames). Anyway, the whole expression matches names that consist entirely of one or two dots, i.e. `/\A[.]{1,2}\z/`
Sinan Ünür
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
+5  A: 

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]/*");
FM
A: 

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
Sorry, this is not a good substitute for `readdir` or `File::Slurp::read_dir`.
Sinan Ünür
Can you explain that? I personally think read_dir is not a good substitute for this ;)
Andomar
A: 

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