3

As of now, I know of two ways to open and read a directory in Perl. You can use opendir, readdir and closedir or you can simply use glob to get the contents of the directory.


Example:

Using opendir, readdir and closedir:

opendir my $dh, $directory;
my @contents = readdir $dh;
closedir $dh;

Using glob:

my @contents = <$directory/*>;

I have been informed that in some situations the glob method can produce unpredictable results (for example it can act differently on different operating systems when it encounters special characters in directory names).

The thing I love about the glob method is how "quick and dirty" it is. It's one simple line and it gets the job done, but if it doesn't work in all situations that can introduce unexpected and difficult to find bugs.

I was wondering if there was a sort of "shorthand" way to use the opendir, readdir, closedir method?

Maybe something like this fancy method for slurping a file in one line I recently discovered.

Community
  • 1
  • 1
tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • I'm curious what the person who informed you thinks will happen in those cases with special characters in directory names. – Andy Lester May 06 '15 at 17:53
  • @AndyLester I think the OP is referring to a comment about spaces in directory names. `mkdir "foo bar" && touch "foo bar/baz" && perl -E '$dir = "foo bar"; say for <$dir/*>'` (can be resolved by quoting the argument to `glob`) – ThisSuitIsBlackNot May 06 '15 at 18:00
  • Here's an excellent write-up of glob vs. readdir: https://stackoverflow.com/questions/1506801/what-reasons-are-there-to-prefer-glob-over-readdir-or-vice-versa-in-perl – Andy Lester May 06 '15 at 18:01
  • @AndyLester, Basically glob would not find files/directories with special characters in the name unless the special characters were escaped properly. How special characters are escaped can be dependent on the operating system. – tjwrona1992 May 06 '15 at 18:11
  • But now that I think of it, wouldn't those special characters potentially trip up `opendir` as well? – tjwrona1992 May 06 '15 at 18:12
  • `sub dir_contents { my ($path) = @_; opendir my $dh, $path or die $!; return readdir $dh }` ;) I know it's tempting, but please don't fall into the trap of always trying to do things in one line. It tends to make your code less readable and harder to debug and maintain. – ThisSuitIsBlackNot May 06 '15 at 18:14
  • @tjwrona1992: I'm pretty sure that what you have described, that glob won't find files/directories with special characters in the name, is incorrect. You can look at `perldoc File::Glob` for more information. – Andy Lester May 06 '15 at 18:20
  • @ThisSuitIsBlackNot Well duhhh I can do it as a subroutine haha, I was just wondering if there was a convenient shortcut like Perl seems to offer us for many other things. I just figured if it can be done with files, why not directories? `:)` – tjwrona1992 May 06 '15 at 18:21
  • @AndyLester ikegami made a comment on [this deleted question](http://stackoverflow.com/questions/30080596/perl-readdir-one-liner). I can't see it, but you should be able to. I'm pretty sure spaces were referenced, but I don't remember anything about other special characters. – ThisSuitIsBlackNot May 06 '15 at 18:26
  • This is the exact comment: "my @contents = <$directory/*>; is buggy. For example, it won't work if $directory contains spaces or a number of other characters. Even if you were to add the needed escaping (which varies by OS), * won't return all the files in the directory on unix systems." – tjwrona1992 May 06 '15 at 18:28
  • 1
    @Andy Lester, That makes no sense; there is no difference between `<...>` and `glob(qq<...>)` (except when `<...>` means `readline(...)`, of course). – ikegami May 06 '15 at 19:09
  • My understanding was that `glob()` was a safer version of `<>`. Mea culpa. – Andy Lester May 06 '15 at 19:59

2 Answers2

2

How about the following?

my @contents = get_dir_contents($dir);

You even get to decide how that handles errors, if . should be returned, if .. should be returned, if "hidden" files should be returned and whether the path is prepended to the file name since you write get_dir_contents yourself.


Alternatives:

  • use File::Find::Rule qw( );
    my @contents = File::Find::Rule->maxdepth(1)->in($dir);
    
  • use File::Slurp qw( read_dir );
    my @contents = read_dir($dir);
    
  • # Unix only, but that includes cygwin and OS/X.
    my @contents = <\Q$dir\E/.* \Q$dir\E/*>;
    
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • I like [`File::Slurp::read_dir`](https://metacpan.org/pod/File::Slurp#read_dir), although I understand `File::Slurp` has fallen out of favor in some circles. – Jim Davis May 06 '15 at 19:02
  • 1
    @Jim Davis, File::Slurp used to be buggy as hell, so I've always avoided it. I didn't even know it had such a sub. I'm my understanding that these problems were fixed long ago now, however. I've added it to my answer. – ikegami May 06 '15 at 19:05
  • @ikegami, A subroutine is the obvious solution, this is more of a theoretical question. I want the simplest most bare-bones possible way of reading a directory. Basically it would just give you the default output of `readdir` without all the clunkiness of having a line of `opendir` to get the handle followed by a line of `readdir` to read the handle followed by a line of `closedir` to close the handle. – tjwrona1992 May 06 '15 at 20:33
  • 1
    I gave you four such solutions. – ikegami May 06 '15 at 21:25
  • What package is get_dir_contents even in? – felwithe Nov 05 '17 at 13:59
  • @felwithe, whichever package you decide to put it in, since you're the one writing it. – ikegami Nov 05 '17 at 15:17
  • @ikegami that's not an answer. – felwithe Nov 05 '17 at 18:28
0

I believe I have come up with a valid one-liner that involves opendir/readdir!

my @contents = do { opendir my $dh, $dir or die $!; readdir $dh };

This should open and read the directory, return all of its contents and as soon as the do block ends, the $dh should be closed by Perl "automagically".

ikegami
  • 367,544
  • 15
  • 269
  • 518
tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • How is this substantively different from `opendir my $dh, $dir; my @contents = readdir $dh;`? You don't need `do` to put multiple statements on one line. – ThisSuitIsBlackNot May 06 '15 at 20:38
  • 1
    The `do {}` makes the `my ($dh)` locally scoped so that the file handle will be closed as soon as it gets out of scope. It also allows the declaration of `my @contents` to be outside of that scope thus it can be declared on the same line and still be used non-locally. If you tried to locally scope it without the `do {}` you'd get `{ opendir my $dh, $dir; my @contents = readdir $dh; }` but then you run into the problem of not being able to use `@contents` on the next line because it would now be out of scope! ...Its quite beautiful and graceful actually. Perl is amazing. `:)` – tjwrona1992 May 06 '15 at 20:44
  • Not closing a lexical directory handle immediately is [rarely significant](http://stackoverflow.com/questions/3687198/what-are-the-implications-of-not-closing-a-directory-handle-in-perl), so I don't think what you've posted is *substantively* different from simply putting multiple statements on one line. But since you already know this is poor practice, I'll stop harping on it :) – ThisSuitIsBlackNot May 06 '15 at 20:57
  • The OP specifically asked for something shorter than `opendir`+`readdir`+`closedir`. Technically, removing `closedir` is shorter, but don't you think the OP knew that? Also, four other shorter solutions have already been provided, so I'm not sure how you can consider this a shorthand. – ikegami May 06 '15 at 21:48
  • Well creating your own subroutine is hardly shorthand, and using another module is almost like writing your own subroutine (except that it has been tested and you know it will work). Of your answers, using File::Slurp is probably the shortest and simplest implementation, but the intent of this question was to find my own way to do it. It's more about learning rather than it is about actual usability. Basically I knew I could get it in one line using glob, but I wanted to know if there was a short alternative to globbing using `opendir`/`readdir`/`closedir`. – tjwrona1992 May 07 '15 at 03:33
  • Also, the reason i left off the `or die` in my original answer was to emphasize how nice and short the code was `:)` obviously adding `or die` is better practice. – tjwrona1992 May 07 '15 at 03:36