16

I'm using this code to get a list of all the files in a specific directory:

opendir DIR, $dir or die "cannot open dir $dir: $!";
my @files= readdir DIR;
closedir DIR;

How can I modify this code or append something to it so that it only looks for text files and only loads the array with the prefix of the filename?

Example directory contents:

.
..
923847.txt
98398523.txt
198.txt
deisi.jpg
oisoifs.gif
lksdjl.exe

Example array contents:

files[0]=923847 
files[1]=98398523
files[2]=198
brian d foy
  • 129,424
  • 31
  • 207
  • 592
CheeseConQueso
  • 5,831
  • 29
  • 93
  • 126
  • 1
    Also consider using a lexical variable for your directory handle: `opendir my $dirh, $dir_path or die "cannot open dir $dir: $!";` – Robert P Oct 02 '09 at 16:30

6 Answers6

12
my @files = glob "$dir/*.txt";
for (0..$#files){
  $files[$_] =~ s/\.txt$//;
}
Wooble
  • 87,717
  • 12
  • 108
  • 131
  • any idea how to regex the directory out too? my output is /dir/dir/dir/923847... how can i just get the 923847? – CheeseConQueso Oct 02 '09 at 15:50
  • glob adds extra work here. See http://stackoverflow.com/questions/1506801/what-reasons-are-there-to-prefer-glob-over-readdir-or-vice-versa-in-perl – brian d foy Oct 02 '09 at 16:09
6

it is enough to change one line:

my @files= map{s/\.[^.]+$//;$_}grep {/\.txt$/} readdir DIR;
catwalk
  • 6,340
  • 25
  • 16
5

If you can use the new features of Perl 5.10, this is how I would write it.

use strict;
use warnings;
use 5.10.1;
use autodie; # don't need to check the output of opendir now

my $dir = ".";

{
  opendir my($dirhandle), $dir;
  for( readdir $dirhandle ){ # sets $_
    when(-d $_ ){ next } # skip directories
    when(/^[.]/){ next } # skip dot-files

    when(/(.+)[.]txt$/){ say "text file: ", $1 }
    default{
      say "other file: ", $_;
    }
  }
  # $dirhandle is automatically closed here
}

Or if you have very large directories, you could use a while loop.

{
  opendir my($dirhandle), $dir;
  while( my $elem = readdir $dirhandle ){
    given( $elem ){ # sets $_
      when(-d $_ ){ next } # skip directories
      when(/^[.]/){ next } # skip dot-files

      when(/(.+)[.]txt$/){ say "text file: ", $1 }
      default{
        say "other file: ", $_;
      }
    }
  }
}
Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
3

This is the simplest way I've found (as in human readable) using the glob function:

 # Store only TXT-files in the @files array using glob
 my @files = grep ( -f ,<*.txt>);
 # Write them out
 foreach $file (@files) {
  print "$file\n";
 }

Additionally the "-f" ensures that only actual files (and not directories) are stored in the array.

Kebman
  • 1,901
  • 1
  • 20
  • 32
  • Why roll back the edit? `foreach $file` is not strict safe. If you prefer `foreach` to `for`, why not `foreach my $file`? – Benjamin W. Jul 27 '15 at 14:22
  • Why edit somebody elses old stuff when you don't know the reason for why it was posted in the first place? Hell I don't even know the reason why I wrote this piece of code over three years ago, but since I have a habit of testing thing, it probably worked just fine, and there's probably a reason I didn't add in the strict declaration either. However does that mean I should just accept a random edit, and do I even want to spend time testing it? Hell, no! That's why I found it far safer to just roll it back. – Kebman Jul 28 '15 at 09:59
  • Just to be clear, it wasn't me who edited it, I just saw the edit and thought it improved the answer. It's just weird that you use a lexical `@files` and then a global `$file`. – Benjamin W. Jul 28 '15 at 16:00
  • Read it like it's English: For each file in the files list, do something with each file in the list (e.g. print each file entry). I love English because most people understand it. I'm still not going to even try to improve on it myself, and I'm going to roll back all other attempts at it because I don't want to test everybody elses weird changes to my code. However if it's really that shitty I may delete my answer entirely. Instead please use my code and post your own answer. I mean, it's not like it's copyrighted or anything, so go right ahead. :) – Kebman Jul 30 '15 at 08:09
  • > and there's probably a reason I didn't add in the strict declaration either. There's literally no good reason ever to not us strict. Downvoted. – Franz Kafka Oct 05 '17 at 22:32
2

To get just the ".txt" files, you can use a file test operator (-f : regular file) and a regex.

my @files = grep { -f && /\.txt$/ } readdir $dir;

Otherwise, you can look for just text files, using perl's -T (ascii-text file test operator)

my @files = grep { -T } readdir $dir;
Robert P
  • 15,707
  • 10
  • 68
  • 112
  • -T is for testing whether you have a "textfile" – reinierpost Oct 02 '09 at 16:38
  • Good point; from the perldoc page on the operator (http://perldoc.perl.org/5.8.8/perlfunc.html), "-T File is an ASCII text file (heuristic guess)." if he's looking for ".txt" files, this will do exactly what he asks without guessing. – Robert P Oct 02 '09 at 16:44
1

Just use this:

my @files = map {-f && s{\.txt\z}{} ? $_ : ()} readdir DIR;
Hynek -Pichi- Vychodil
  • 26,174
  • 5
  • 52
  • 73
  • Didn't work for me... ""Name "main::DIR" used only once: possible typo at fileOpsDS.pl line 19."" ""readdir() attempted on invalid dirhandle DIR at fileOpsDS.pl line 19."" – George 2.0 Hope Aug 10 '21 at 12:22