Get names of all the directories with similar naming pattern in Perl - perl

I have a directory "logs" which contains sub-directories as "A1", "A2", "A3", "B1", "B2", "B3".
I want to write a perl code that search all the sub directories with name pattern as "A", i.e. all the directories names starting from character A.
Please help me.

Use the Perl core module File::Find:
use strict;
use warnings;
use File::Find;
#Find in 'logs' directory, assume the script is executed at this folder level
find(\&wanted, 'logs');
sub wanted {
#Subroutine called for every file / folder founded ($_ has the name of the current)
if(-d and /^A/ ) {
print $_, "\n";
}
}
Update:
If you want to parametrize the prefix, you can do this:
use strict;
use warnings;
use File::Find;
my $prefix = 'B';
find(\&wanted, 'logs');
sub wanted {
if(-d and /^$prefix/ ) {
print $_, "\n";
}
}

File::Find is overkill for simply searching a directory. opendir/readdir still has a purpose!
This program does a chdir to the specified directory so that there is no need to build the full path from the names generated by readdir.
The directory to search and the required prefix can be passed as command-line parameters and will default to logs and A if they are not supplied.
use strict;
use warnings;
use autodie;
my ($dir, $prefix) = #ARGV ? #ARGV : qw/ logs A /;
chdir $dir;
my #wanted = do {
opendir(my $dh, '.');
grep { -d and /^\Q$prefix/ } readdir $dh;
};
print "$_\n" for #wanted;

Related

Can't call method "_compile" error when using File::Find::Rule

Can't call method "_compile" without a package or object reference at /homes/sauravb/perl5/lib/perl5/File/Find/Rule.pm line 292
#!/usr/bin/perl
use strict;
use warnings;
#use File Perl Module
#use Cwd sub perl module
use File::Find::Rule;
use Cwd;
use Cwd 'chdir';
chdir "/volume/regressions/results/JUNOS/HEAD/EABUPDTREGRESSIONS/15.1/15.1F5";
my $cwd = getcwd();
my $fh;
sub fileindex {
open($fh, ">", "/homes/sauravb/line_with_X-TESTCASE.txt" ) || die $!;
my $rule = File::Find::Rule->new;
my #files = $rule->any (File::Find::Rule->name('*.log'),
File::Find::Rule->in($cwd)
);
#print $fh map{ "$_\n" } #files;
}
fileindex();
any allows you to specify alternate sets of criteria. For example, the following returns all files whose name ends with .log or .txt.
my #files =
File::Find::Rule
->any(
File::Find::Rule->name('*.log'),
File::Find::Rule->name('*.txt'),
)
->in($dir_qfn);
Instead, you passed one criteria plus the names of all the files in a directory to any. That is incorrect.
Did you simply want the list of log files in the directory? If so, you simply need
my #files =
File::Find::Rule
->name('*.log')
->in($dir_qfn);
Or if you want to make sure they're plain files (and not directories),
my #files =
File::Find::Rule
->name('*.log')
->file
->in($dir_qfn);

Traversing Through Directories

I am trying to traverse through directories to change certain file extensions in those directories.
I made it to where I can go through a directory that is given through command-line, but I cannot make it traverse through that directories' subdirectories.
For example: If I want to change the file extensions in the directory Test then if Test has a subdirectory I want to be able to go through that directory and change the file extensions of those files too.
I came up with this. This works for one directory. It correctly changes the file extensions of the files in one specific directory.
#!/usr/local/bin/perl
use strict;
use warnings;
my #argv;
my $dir = $ARGV[0];
my #files = glob "${dir}/*pl";
foreach (#files) {
next if -d;
(my $txt = $_) =~ s/pl$/txt/;
rename($_, $txt);
}
I then heard of File::Find::Rule, so I tried to use that to traverse through the directories.
I came up with this:
#!/usr/local/bin/perl
use strict;
use warnings;
use File::Find;
use File::Find::Rule;
my #argv;
my $dir = $ARGV[0];
my #subdirs = File::find::Rule->directory->in( $dir );
sub fileRecurs{
my #files = glob "${dir}/*pl";
foreach (#files) {
next if -d;
(my $txt = $_) =~ s/pl$/txt/;
rename($_, $txt);
}
}
This does not work/ will not work because I am not familiar enough with File::Find::Rule
Is there a better way to traverse through the directories to change the file extensions?
#!/usr/local/bin/perl
use strict;
use warnings;
use File::Find;
my #argv;
my $dir = $ARGV[0];
find(\&dirRecurs, $dir);
sub dirRecurs{
if (-f)
{
(my $txt = $_) =~ s/pl$/txt/;
rename($_, $txt);
}
}
I figured it out with the help of the tutorial #David sent me! Thank you!

Using Find::File::Rule to find Perl scripts and exclude .bak files

I am using Find::File::Rule to find Perl scripts. I want to exclude certain files, like test files and backup files, but I cannot make it exclude *.bak files if they start with a dot, for example (p.pl):
use warnings;
use strict;
use Data::Dump;
use File::Find::Rule;
open(my $fh,">",".c.bak");
print $fh "#! /usr/bin/env perl\n";
close($fh);
my $rule = File::Find::Rule->new;
$rule->or(
$rule->new->name('*.bak')->prune->discard,
$rule->new->grep( qr/^#!.*perl\s*$/, [ sub { 1 } ] )
);
my #files=$rule->in(".");
dd #files;
This gives output:
("p.pl", ".c.bak")
whereas expected output should be:
"p.pl"
You just have to add another filter rule for the hidden backup files:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dump;
use File::Find::Rule;
open(my $fh,">",".c.bak");
print $fh "#! /usr/bin/env perl\n";
close($fh);
my $rule = File::Find::Rule->new;
$rule->or(
$rule->new->name('*.bak')->prune->discard,
$rule->new->name('.*.bak')->prune->discard, # <== hidden backups
$rule->new->grep( qr/^#!.*perl\s*$/, [ sub { 1 } ] )
);
my #files=$rule->in(".");
dd #files;
Notice the starting . in the pattern.
This script will produce:
"p.pl"
The problem here is that files prefixed with a dot aren't matched by the '*.bak' quantifier, because they're 'hidden' files.
If you chdir to your directory and do echo * or echo *.bak you won't see the file there either. So effectively - that rule isn't matching, because it's a hidden file.
Solutions would be:
new rule to explicitly match '.' files.
regular expression match to 'name' would do the trick
Something like:
$rule->new->name(qr/\.bak$/)->prune->discard,

change the directory and grab the xml file to parse certain data in perl

I am trying to parse specific XML file which is located in sub directories of one directory. For some reason i am getting error saying file does not exists. if the file does not exist it should move on to next sub directory.
HERE IS MY CODE
use strict;
use warnings;
use Data::Dumper;
use XML::Simple;
my #xmlsearch = map { chomp; $_ } `ls`;
foreach my $directory (#xmlsearch) {
print "$directory \n";
chdir($directory) or die "Couldn't change to [$directory]: $!";
my #findResults = `find -name education.xml`;
foreach my $educationresults (#findResults){
print $educationresults;
my $parser = new XML::Simple;
my $data = $parser->XMLin($educationresults);
print Dumper($data);
chdir('..');
}
}
ERROR
music/gitar/education.xml
File does not exist: ./music/gitar/education.xml
Using chdir the way you did makes the code IMO less readable. You can use File::Find for that:
use autodie;
use File::Find;
use XML::Simple;
use Data::Dumper;
sub findxml {
my #found;
opendir(DIR, '.');
my #where = grep { -d && m#^[^.]+$# } readdir(DIR);
closedir(DIR);
File::Find::find({wanted => sub {
push #found, $File::Find::name if m#^education\.xml$#s && -f _;
} }, #where);
return #found;
}
foreach my $xml (findxml()){
say $xml;
print Dumper XMLin($xml);
}
Whenever you find yourself relying on backticks to execute shell commands, you should consider whether there is a proper perl way to do it. In this case, there is.
ls can be replaced with <*>, which is a simple glob. The line:
my #array = map { chomp; $_ } `ls`;
Is just a roundabout way of saying
chomp(my #array = `ls`); # chomp takes list arguments as well
But of course the proper way is
my #array = <*>; # no chomp required
Now, the simple solution to all of this is simply to do
for my $xml (<*/education.xml>) { # find the xml files in dir 1 level up
Which will cover one level of directories, with no recursion. For full recursion, use File::Find:
use strict;
use warnings;
use File::Find;
my #list;
find( sub { push #list, $File::Find::name if /^education\.xml$/i; }, ".");
for (#list) {
# do stuff
# #list contains full path names of education.xml files found in subdirs
# e.g. ./music/gitar/education.xml
}
You should note that changing directories is not required, and in my experience, not worth the trouble. Instead of doing:
chdir($somedir);
my $data = XMLin($somefile);
chdir("..");
Simply do:
my $data = XMLin("$somedir/$somefile");

How do I use chdir to traverse subdirectories and parse XML files?

I want to write a script that traverses a directory and its subdirectories, grabs all the XML files and parses them. I am having trouble with chdir. This works fine:
my $search = "/home/user/books";
chdir($search) or die "cant change dir to $search $!";
system("ls");
But I want the user to decide the path where he want to search it so I am using Getopt::Long:
use strict;
use warnings;
use Data::Dumper;
use XML::Simple;
use Getopt::Long;
my $outputFile = '';
my $searchPath = "";
my $debug = 0;
GetOptions('outputFile=s' => \$outputFile, 'searchPath=s' => \$searchPath);
if ($outputFile eq '' or $searchPath = '') {
die("parameter --outpulFile=s is required.");
}
$searchPath =~ s/\/*$/\//;
my #founddirs = `cd $searchPath`;
foreach my $foundfiles (#founddirs) {
print $foundfiles;
chdir($foundfiles) or die "cant change dir to $searchPath $!";
chdir('..');
}
Command to run:
perl sample.pl --outputFile=books.txt --searchPath=/home/user/june18
I want to grab all the recursive.xml files from the subdirectories and parse them. Does anyone know how this can be done?
A couple of issues here:
$searchPath = '' is setting the search path to an empty string during the input validation. Use eq instead (not ==)
#founddirs will contain nothing since the backtick operator will return nothing. This is because
my #founddirs = `cd $searchPath`;
does not print found directories that are separated by newlines. Perhaps you're after ls $searchPath
On a side note, why not use File::Find instead?
use strict;
use warnings;
use File::Find;
use Getopt::Long;
my $outputFile;
my $searchPath;
GetOptions(
'outputFile=s' => \$outputFile,
'searchPath=s' => \$searchPath,
);
die "Usage : perl sample.pl -outputFile -searchPath\n"
unless $outputFile && $searchPath;
die "No such directory found: $searchPath\n" unless -d $searchPath;
find( sub { print "$File::Find::name\n" if /$outputFile/ }, $searchPath );
#!/usr/bin/perl --
use strict; use warnings;
use Data::Dump qw/ dd /;
use File::Find::Rule qw/ find /;
my #files = find(
file =>
name => '*.xml',
in => \#ARGV
);
dd \#files;
__END__
$ perl ffrule
[]
$ perl ffrule ../soap
[
"../soap/ex1.xml",
"../soap/ex2.xml",
"../soap/ex3.xml",
]