Find files in the folder and subfolder - perl

I am using next if $file eq '.' $file eq '..'; to find the file in the directory and subdirectory(except few directories) and opening the files for find and replacement. But when I have dot in folder name, it consider the folder as a file and says can't open. I filtered the files using -f but it missing to show the files in the main folder.
Is there any recursive way to find the folder and files even it has dot.
opendir my $dh, $folder or die "can't open the directory: $!";
while ( defined( my $file = readdir( $dh ) ) ) {
chomp $file;
next if $file eq '.' $file eq '..';
{
if ( $file ne 'fp' ) {
print "$folder\\$file";
if ( $file =~ m/(.[^\.]*)\.([^.]+$)/ ) {
...
}
}
}
}

You could use File::Find or File::Find::Rule as suggested by Sobrique.
It's very easy to use:
#!/usr/bin/perl
use strict;
use warnings;
use File::Find;
sub process_file {
next if (($_ eq '.') || ($_ eq '..'));
if (-d && $_ eq 'fp'){
$File::Find::prune = 1;
return;
}
print "Directory: $_\n" if -d;
print "File: $_\n" if -f;
#Do search replace operations on file below
}
find(\&process_file, '/home/chankeypathak/Desktop/test.folder'); #provide list of paths as second argument.
I had below file structure.
test.folder/test.txt
test.folder/sub.folder
test.folder/sub.folder/subfile.txt
test.folder/fp
test.folder/fp/fileinsidefp.txt
And I got below output
$ perl test.pl
File: test.txt
Directory: sub.folder
File: subfile.txt

Yes. Use File::Find::Rule
foreach my $file ( File::Find::Rule->file()->in( "." ) ) {
}
... and that's about it. You've got options for pretty much all the 'filetest' flags, so file() for -f or readable() for -r.

Related

Perl copying files from one directory to another

I'm trying to copy files from multiple directories with the code bellow. It prints out the correct path and files but fails to copy them. Please suggest how to fix this issue? Thanks
#!/usr/bin/perl
use strict;
use warnings;
use File::Copy;
my $target_dir = "";
my #dirs = grep { -d } glob '/data/results/*';
for my $source_dir ( #dirs ) {
opendir(my $DIR, $source_dir) || die "can't opendir $source_dir: $!";
my #files = readdir($DIR);
print "the directory is $source_dir\n";
my $run_folder = (split '/', $source_dir)[3];
print "the folder is $run_folder\n";
$target_dir = "/data/backup/$run_folder";
print $target_dir;
foreach my $t (#files)
{
if(-f "$source_dir/$t" ) {
#Check with -f only for files (no directories)
print "$source_dir/$t";
print "$target_dir/$t";
copy "$source_dir/$t", "$target_dir/$t";
}
}
closedir($DIR);
}
There are a few things I would recommend you to do:
Close your file handles as soon as possible if you are not using it anymore:
opendir(my $DIR, $source_dir) || die "can't opendir $source_dir: $!";
my #files = readdir($DIR);
close ($DIR);
As you are trying to backup some files and directories maybe the target destination will not have the directory so:
$target_dir = "/data/backup/$run_folder";
print $target_dir;
if ( ! -d $target_dir )
{
#creates the dir
}
And the last one:
foreach my $t (#files)
{
chomp $t; # it removes any new line
if(-f "$source_dir/$t" ) {
#Check with -f only for files (no directories)
print "$source_dir/$t";
print "$target_dir/$t";
if ( ! copy "$source_dir/$t", "$target_dir/$t" )
{
print "Some error: $!";
}
}
}
Always TIMTOWTD, you could use File::Find which has a simple tutorial here.

Why is a directory listing not working on my Windows system?

Why is the below code is not working?
-d and -f are not functioning on my Windows machine.
use strict;
use warnings;
use Data::Dump qw/pp/;
my $path="C:/perl/workspace";
opendir ( DIR, $path ) || die "Error in opening dir $path\n";
my (#file,#dir);
while (my $filename=readdir(DIR)) {
next if ($filename =~ m/^\./);
if (-f $filename) {
push(#file,$filename);
} elsif (-d $filename){
push(#dir,$filename);
}
}
#pp \#file,\#dir;
print "#dir";
Check for file with path,
if (-f "$path/$filename")
You should add the $path to the $filename before checking if it's a file or directory as readdir only returns the filenames without the path.
...
if (-f $path."/".$filename) {
push(#file,$filename);
} elsif (-d $path."/".$filename){
push(#dir,$filename);
}
...

How to check in Perl whether there is any sub folder exist in a folder?

Is there a way to check whether there is any subfolder exist inside a folder. I would like to do this in Perl?
Glob through the contents of the directory, and check whether it is a directory with -d.
sub has_subfolder {
my $directory = shift;
for ( <$directory/*>, <$directory/.*> ) {
next if m#/\.\.?$#; # skip . and ..
return 1 if -d;
}
return 0;
}
if (grep -d, glob("$folder/*")) {
print "$folder has subfolder(s)\n";
}
If you want to deal with directories matching .*, you could do:
if (grep -d && !/\.\.?$/, glob("$folder/.* $folder/*")) {
print "$folder has subfolder(s)\n";
}
You can use the'File::Find' module for this purpose. File::Find processes and scans a directory recursively. Here is the sample code:
use File::Find;
my $DirName = 'dirname' ;
sub has_subdir
{
#The path of the file/dir being visited.
my $subdir = $File::Find::name;
#Ignore if this is a file.
return unless -d $subdir;
#Ignore if $subdir is $Dirname itself.
return if ( $subdir eq $DirName);
# if we have reached here, this is a subdirector.
print "Sub directory found - $subdir\n";
}
#For each file and sub directory in $Dirname, 'find' calls
#the 'has_subdir' subroutine recursively.
find (\&has_subdir, $DirName);
In order to check if a subfolder exists in a directory (without knowing any names):
my $dir_name = "some_directory";
opendir my $dir, $dir_name
or die "Could not open directory $dir_name: $!";
my $has_subfolder = grep { -d && !/(^|\/)\.\.?$/ } map { ("$dir_name"||'.')."/$_" } readdir $dir;
In other words, it checks for one or more files in the directory which are themselves directories.
If you want a specific subfolder, just use Geo's answer.
Edit: This is getting silly now, but here's a truly general-purpose answer. :-P Someone else is getting the check mark anyway.
sub hasSubDir {
my $dir_name = shift;
opendir my $dir, $dir_name
or die "Could not open directory $dir_name: $!";
my #files = readdir($dir);
closedir($dir);
for my $file (#files) {
if($file !~ /\.\.?$/) {
return 1 if -d $dir/$file;
}
}
return 0;
}
OK, I'm just gonna have to submit my own answer
sub has_subfolder {
my $dir = shift;
my $found = 0;
opendir my $dh, $dir or die "Could not open directory $dir: $!";
while (my $_ = readdir($dh)) {
next if (/^\.\.?$/); # skip '.' and '..'
my $path = $dir . '/' . $_; # readdir doesn't return the whole path
if (-d $path) { # found a dir? record it, and leave the loop!
$found = 1;
last;
}
closedir($dh); # make sure we cleanup after!
return $found;
}
Compared to other answers:
finds hidden directories
completes as soon as it finds a match
doesn't traverse the tree twice (once for normal files, and again for hidden files)
EDIT - I see the requirements just changed (sigh). Fortunately the code above is trivially modified:
sub get_folders {
my $dir = shift;
my #found;
opendir my $dh, $dir or die "Could not open directory $dir: $!";
while (my $_ = readdir($dh)) {
next if (/^\.\.?$/); # skip '.' and '..'
my $path = $dir . '/' . $_; # readdir doesn't return the whole path
push(#found, $_) if (-d $path) # found a dir? record it
}
closedir($dh); # make sure we cleanup after!
return #found;
}
if(-e "some_folder/some_subfolder") {
print "folder exists";
}
else {
print "folder does not exist";
}

How can I get a list of all files with a certain extension from a specific directory?

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
my #files = glob "$dir/*.txt";
for (0..$#files){
$files[$_] =~ s/\.txt$//;
}
it is enough to change one line:
my #files= map{s/\.[^.]+$//;$_}grep {/\.txt$/} readdir DIR;
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: ", $_;
}
}
}
}
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.
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;
Just use this:
my #files = map {-f && s{\.txt\z}{} ? $_ : ()} readdir DIR;

How do I read in the contents of a directory in Perl?

How do I get Perl to read the contents of a given directory into an array?
Backticks can do it, but is there some method using 'scandir' or a similar term?
opendir(D, "/path/to/directory") || die "Can't open directory: $!\n";
while (my $f = readdir(D)) {
print "\$f = $f\n";
}
closedir(D);
EDIT: Oh, sorry, missed the "into an array" part:
my $d = shift;
opendir(D, "$d") || die "Can't open directory $d: $!\n";
my #list = readdir(D);
closedir(D);
foreach my $f (#list) {
print "\$f = $f\n";
}
EDIT2: Most of the other answers are valid, but I wanted to comment on this answer specifically, in which this solution is offered:
opendir(DIR, $somedir) || die "Can't open directory $somedir: $!";
#dots = grep { (!/^\./) && -f "$somedir/$_" } readdir(DIR);
closedir DIR;
First, to document what it's doing since the poster didn't: it's passing the returned list from readdir() through a grep() that only returns those values that are files (as opposed to directories, devices, named pipes, etc.) and that do not begin with a dot (which makes the list name #dots misleading, but that's due to the change he made when copying it over from the readdir() documentation). Since it limits the contents of the directory it returns, I don't think it's technically a correct answer to this question, but it illustrates a common idiom used to filter filenames in Perl, and I thought it would be valuable to document. Another example seen a lot is:
#list = grep !/^\.\.?$/, readdir(D);
This snippet reads all contents from the directory handle D except '.' and '..', since those are very rarely desired to be used in the listing.
A quick and dirty solution is to use glob
#files = glob ('/path/to/dir/*');
This will do it, in one line (note the '*' wildcard at the end)
#files = </path/to/directory/*>;
# To demonstrate:
print join(", ", #files);
IO::Dir is nice and provides a tied hash interface as well.
From the perldoc:
use IO::Dir;
$d = IO::Dir->new(".");
if (defined $d) {
while (defined($_ = $d->read)) { something($_); }
$d->rewind;
while (defined($_ = $d->read)) { something_else($_); }
undef $d;
}
tie %dir, 'IO::Dir', ".";
foreach (keys %dir) {
print $_, " " , $dir{$_}->size,"\n";
}
So you could do something like:
tie %dir, 'IO::Dir', $directory_name;
my #dirs = keys %dir;
You could use DirHandle:
use DirHandle;
$d = new DirHandle ".";
if (defined $d)
{
while (defined($_ = $d->read)) { something($_); }
$d->rewind;
while (defined($_ = $d->read)) { something_else($_); }
undef $d;
}
DirHandle provides an alternative, cleaner interface to the opendir(), closedir(), readdir(), and rewinddir() functions.
Similar to the above, but I think the best version is (slightly modified) from "perldoc -f readdir":
opendir(DIR, $somedir) || die "can't opendir $somedir: $!";
#dots = grep { (!/^\./) && -f "$somedir/$_" } readdir(DIR);
closedir DIR;
You can also use the children method from the popular Path::Tiny module:
use Path::Tiny;
my #files = path("/path/to/dir")->children;
This creates an array of Path::Tiny objects, which are often more useful than just filenames if you want to do things to the files, but if you want just the names:
my #files = map { $_->stringify } path("/path/to/dir")->children;
Here's an example of recursing through a directory structure and copying files from a backup script I wrote.
sub copy_directory {
my ($source, $dest) = #_;
my $start = time;
# get the contents of the directory.
opendir(D, $source);
my #f = readdir(D);
closedir(D);
# recurse through the directory structure and copy files.
foreach my $file (#f) {
# Setup the full path to the source and dest files.
my $filename = $source . "\\" . $file;
my $destfile = $dest . "\\" . $file;
# get the file info for the 2 files.
my $sourceInfo = stat( $filename );
my $destInfo = stat( $destfile );
# make sure the destinatin directory exists.
mkdir( $dest, 0777 );
if ($file eq '.' || $file eq '..') {
} elsif (-d $filename) { # if it's a directory then recurse into it.
#print "entering $filename\n";
copy_directory($filename, $destfile);
} else {
# Only backup the file if it has been created/modified since the last backup
if( (not -e $destfile) || ($sourceInfo->mtime > $destInfo->mtime ) ) {
#print $filename . " -> " . $destfile . "\n";
copy( $filename, $destfile ) or print "Error copying $filename: $!\n";
}
}
}
print "$source copied in " . (time - $start) . " seconds.\n";
}
from: http://perlmeme.org/faqs/file_io/directory_listing.html
#!/usr/bin/perl
use strict;
use warnings;
my $directory = '/tmp';
opendir (DIR, $directory) or die $!;
while (my $file = readdir(DIR)) {
next if ($file =~ m/^\./);
print "$file\n";
}
The following example (based on a code sample from perldoc -f readdir) gets all the files (not directories) beginning with a period from the open directory. The filenames are found in the array #dots.
#!/usr/bin/perl
use strict;
use warnings;
my $dir = '/tmp';
opendir(DIR, $dir) or die $!;
my #dots
= grep {
/^\./ # Begins with a period
&& -f "$dir/$_" # and is a file
} readdir(DIR);
# Loop through the array printing out the filenames
foreach my $file (#dots) {
print "$file\n";
}
closedir(DIR);
exit 0;
closedir(DIR);
exit 0;