Perl: How to search a file named ".cfg" in a directory and all it's parent directories - perl

How to search a file named ".cfg" in a directory and all it's parent directories
I am fetching the name as below code but i would like to know if there is any better way to do it.
Also i would like to know the recursive way to do the same.
sub get_p4_config_updir($ $)
{
my ($client_root, $cfg_file) = #_;
# Dir from where search starts - it's a client root here
my $cur_dir = $client_root;
printf("**** cur_dir: $cur_dir ****\n");
my $slashes = $cur_dir =~ y/\///;
printf("**** no of back slashes: $slashes ****\n");
while($slashes > 2) {
my ($parent_dir, $b) = $cur_dir =~ /(.*)\/(.*)/;
printf("**** parent_dir: $parent_dir, b: $b ****\n");
$slashes--;
if (-e "$cur_dir/$cfg_file") {
printf("**** File exists in dir: $cur_dir ****\n");
return $cur_dir;
}
$cur_dir = $parent_dir;
}
return "";
}
my $cfg = '.cfg';
my $dir = '/user/home/wkspace/abc/def/MAIN';
my $path = get_p4_config_updir($dir, $cfg);
if ($path ne "") {
printf("**** File exists in dir: $path ****\n");
} else {
printf("**** File not found ****\n");
}

An example using Path::Tiny:
#!/usr/bin/env perl
use warnings;
use strict;
use feature qw/say/;
use Path::Tiny;
# Returns a Path::Tiny object to the directory containing the file
# being looked for, or undef if not found.
sub get_p4_config_updir {
my ($client_root, $cfg_file) = #_;
my $dir = path($client_root)->realpath;
while (1) {
# say "Looking at $dir";
if ($dir->child($cfg_file)->exists) {
return $dir;
} elsif ($dir->is_rootdir) {
return undef;
} else {
$dir = $dir->parent;
}
}
}
my $cfg = '.cfg';
my $dir = '/user/home/wkspace/abc/def/MAIN';
say get_p4_config_updir($dir, $cfg) // "File not found";
Or a version that's similar to #rajashekar's idea of walking the directory tree by using chdir to get each directory's parent. This one uses File::chdir, which lets you localize changes to the current working directory (and restores the original when the function/scope exits), as well as providing a handy array view of the current directory and its parents that can be manipulated:
use File::chdir;
...
sub get_p4_config_updir {
my ($client_root, $cfg_file) = #_;
local $CWD = $client_root; # Magic happens here
while (1) {
# say "Looking at $CWD";
if (-e $cfg_file) {
return $CWD;
} elsif ($CWD eq "/") {
return undef;
} else {
pop #CWD; # CDs to the next parent directory
}
}
}

You can use core libraries to do this in a platform independent, readable way without having to use cwd and possibly causing action at a distance effects in the rest of your code:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec::Functions qw(catfile rel2abs updir);
sub get_p4_config_updir
{
my ($dir, $file) = #_;
$dir = rel2abs($dir);
do {
my $path = catfile $dir => $file;
return $dir if -e $path;
return if $dir eq (my $new_dir = rel2abs(catfile $dir, updir));
$dir = $new_dir;
} while ('NOT_DONE');
return;
}
sub main {
my ($cfg, $dir) = #_;
my $path = get_p4_config_updir($dir, $cfg);
if (defined $path) {
printf("Found '%s' in '%s'\n", $cfg, $path);
}
else {
printf(
"Did not find '%s' in '%s' or any of its parent directories\n",
$cfg,
$dir,
);
}
}
main(#ARGV);
Output:
C:\Users\u\AppData\Local\Temp> perl p.pl linux.bin .
Found 'linux.bin' in 'C:\'

Why deal with pathnames, when you can walk the directory structure up with .. ?
if the file exists in the current directory return it.
else go up .. and the repeat the process.
use Cwd qw(cwd);
sub search_up {
my ($dir, $file) = #_;
chdir($dir);
while (1) {
if (-e $file) {
print "$file exists in $dir\n";
return $dir;
} elsif ($dir eq "/") {
return;
} else {
chdir("..");
$dir = cwd;
}
};
}

Please see if following code snippet complies with your requirements.
The script is looking for configuration file toward root of filesystem, found filenames are stored in an array #found.
use strict;
use warnings;
use feature 'say';
my $dir = '/user/home/wkspace/abc/def/MAIN';
my $ext = 'cfg';
my($cwd,#found);
for( split('/',$dir) ) {
$cwd .= "$_/";
push #found, glob( $cwd . "*\.$ext" );
}
if( #found ) {
say for #found;
} else {
say 'No file(s) was found';
}
exit 0;
Following code snippet is looking for configuration files away from root filesystem starting from $dir.
If any files found then they will be stored under array reference $found and then printed out on the terminal.
If no files get found then you will be informed with a message.
use strict;
use warnings;
use feature 'say';
my $dir = '/user/home/wkspace/abc/def/MAIN';
my $ext = 'cfg';
my $found = find($dir,$ext);
if( $found ) {
say for #$found;
} else {
say 'No file(s) was found';
}
exit 0;
sub find {
my $dir = shift;
my $ext = shift;
my $ret;
for( glob("$dir/*") ) {
push #$ret, $_ if /\.$ext\z/;
if( -d ) {
my $found = find($_,$ext);
push #$ret, #$found if $found;
}
}
return $ret;
}

Related

Find the biggest file in a directory. Perl

The program lists all files in a directory, their size, type and owner. In case file is a directory, owner is the owner of the biggest file in that directory (that's the problem).
use warnings;
use strict;
use diagnostics;
use feature 'say';
use File::Find;
my $dir = shift || die "Provide a directory name";
my #file;
my #owner;
my #size;
my #type;
my $i = 0;
while( glob("$dir/*") )
{
$file[$i] = $_;
find(sub { $size[$i] += -s if -f }, $_);
if( -d $file[$i] )
{
$type[$i] = "d";
$owner[$i] = getpwuid((stat($_))[4]);
}
elsif ( -l $file[$i] )
{
$type[$i] = "l";
$owner[$i] = getpwuid((stat($_))[4]);
}
else
{
$type[$i] = "f";
$owner[$i] = getpwuid((stat($_))[4]);
}
print "$file[$i]:$owner[$i]:$type[$i]:$size[$i]\n";
$i++;
}
At this point in code
if( -d $file[$i] )
{
$type[$i] = "d";
$owner[$i] = getpwuid((stat($_))[4]);
}
i have to find the biggest file in this directory. I figured, that i should use find function, but not sure on how to do it.
Please investigate the following code piece for compliance with your task.
The code uses recursion for directories, core component is glob function.
The result of directory lookup is returned as hash reference. Fill free to utilize this reference as your heart desire.
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my $dir = shift || die "Provide directory";
my $result = dir_lookup($dir);
say Dumper($result);
exit 0;
sub dir_lookup {
my $dir = shift;
my($record,$max);
my #items = glob("$dir/*");
$max = 0;
for my $name ( #items ) {
my $data;
$data->{name} = $name;
$data->{size} = -s $name;
$data->{owner} = getpwuid((stat($name))[4]);
$data->{type} = 'link' if -l $name;
$data->{type} = 'file' if -f $name;
$data->{type} = 'dir' if -d $name;
if( $data->{size} > $max and -f $name ) {
$max = $data->{size};
$record->{file} = $data;
}
if( $data->{type} eq 'dir' ) {
my $r = dir_lookup($data->{name});
$data->{file} = $r->{file};
$data->{owner} = $r->{file}{owner};
}
push #{$record->{items}}, $data;
}
return $records;
}

How to find the class-file on case insensitive filesystem?

Simple test case (for the demonstration of the problem):
mkdir -p ./lib1/Class ./lib2/Class
touch ./lib1/Class/Name.pm ./lib2/Class/NAME.pm
So, have:
./lib1/Class/Name.pm
./lib2/Class/NAME.pm
Need search for the right file in the case-insensitive filesystem (OS X's HFS+).
The following works on case-sensitive filesystem,
#!/usr/bin/env perl
use 5.014;
use strict;
use warnings;
my #DIRS = qw(./lib1 ./lib2);
for my $class ( qw(Class::Name Class::NAME) ) {
my $file = findClassFile($class);
say $file;
}
sub findClassFile {
my($file) = #_;
$file =~ s|::|/|g;
$file .= ".pm";
for my $dir (#DIRS) {
return "$dir/$file" if( -e "$dir/$file" );
}
return undef;
}
and prints
./lib1/Class/Name.pm
./lib2/Class/NAME.pm
on the OS X, it prints incorrectly:
./lib1/Class/Name.pm
./lib1/Class/NAME.pm
How to find on the OSX's insensitive filesystem the correct filename?
Ps: Now only comes to my mind write and recursive routine with opendir/readdir/chdir and checking the filenames what are comes from readdir. Not to shabby... Exists some more easy way?
My current solution is:
#!/usr/bin/env perl
use 5.014;
use strict;
use warnings;
my #DIRS = qw(./lib1 ./lib2 /Users/me/tmp/lib3);
for my $class ( qw(Class::Name Class::NAME CLASS::name Class::Namex) ) {
my $file = findClassFile($class);
say $file // "Not found $class";
}
sub findClassFile {
my($classname) = #_;
my $file = ($classname =~ s|::|/|gr) . ".pm";
for my $dir (#DIRS) {
return "$dir/$file" if( FileExists("$dir/$file") );
}
return undef;
}
sub FileExists {
my($path) = #_;
my $curr = $path =~ m|^/| ? "/" : ".";
for my $part (split '/', $path) {
next unless $part;
opendir(my $dfd, $curr) || return undef;
my #files = grep {/^$part$/} readdir($dfd);
closedir($dfd);
return undef unless( #files );
$curr .= "/$part";
}
return $curr;
}
what prints:
./lib1/Class/Name.pm
./lib2/Class/NAME.pm
/Users/me/tmp/lib3/CLASS/name.pm
Not found Class::Namex
so - it's working, only don't like it.. ;)

Can't find file trying to move

I'm trying to clean up a directory that contains a lot of sub directories that actually belong in some of the sub directories, not the main directory.
For example, there is
Main directory
sub1
sub2
sub3
HHH
And HHH belongs in sub3. HHH has multiple text files inside of it (as well as some ..txt and ...txt files that I would like to ignore), and each of these text files has a string
some_pattern [sub3].
So, I attempted to write a script that looks into the file and then moves it into its corresponding directory
use File::Find;
use strict;
use warnings;
use File::Copy;
my $DATA = "D:/DATA/DATA_x/*";
my #dirs = grep { -d } glob $DATA;
foreach (#dirs) {
if ($_ =~ m/HHH/) {
print "$_\n";
my $file = "$_/*";
my #files = grep { -f } glob $file;
foreach (#files) {
print "file $_\n";
}
foreach (#files) {
print "\t$_\n";
my #folders = split('/', $_);
if ($folders[4] eq '..txt' or $folders[4] eq '...txt') {
print "$folders[4] ..txt\n";
}
foreach (#folders) {
print "$_\n";
}
open(FH, '<', $_);
my $value;
while (my $line = <FH>) {
if ($line =~ m/some_pattern/) {
($value) = $line =~ /\[(.+?)\]/;
($value) =~ s/\s*$//;
print "ident'$value'\n";
my $new_dir = "$folders[0]/$folders[1]/$folders[2]/$value/$folders[3]/$folders[4]";
print "making $folders[0]/$folders[1]/$folders[2]/$value/$folders[3]\n";
print "file is $folders[4]\n";
my $new_over_dir = "$folders[0]/$folders[1]/$value/$folders[2]/$folders[3]";
mkdir $new_over_dir or die "Can't make it $!";
print "going to swap\n '$_'\n for\n '$new_dir'\n";
move($_, $new_dir) or die "Can't $!";
}
}
}
}
}
It's saying
Can't make it No such file or directory at foo.pl line 57, <FH> line 82.
Why is it saying that it won't make a file that doesn't exist?
A while later: here is my final script:
use File::Find;
use strict;
use warnings;
use File::Copy;
my $DATA = "D:/DATA/DATA_x/*";
my #dirs = grep { -d } glob $DATA;
foreach (#dirs) {
if ($_ =~ m/HHH/) {
my $value;
my #folders;
print "$_\n";
my $file = "$_/*";
my #files = grep { -f } glob $file;
foreach (#files) {
print "file $_\n";
}
foreach (#files) {
print "\t$_\n";
#folders = split('/', $_);
if ($folders[4] eq '..txt' or $folders[4] eq '...txt') {
print "$folders[4] ..txt\n";
}
foreach (#folders) {
print "$_\n";
}
open(FH, '<', $_);
while (my $line = <FH>) {
if ($line =~ m/some_pattern/) {
($value) = $line =~ /\[(.+?)\]/;
($value) =~ s/\s*$//;
print "ident'$value'\n";
}
}
}
if($value){
print "value $value\n";
my $dir1 = "/$folders[1]/$folders[2]/$folders[3]/$folders[4]/$folders[5]";
my $dir2 = "/$folders[1]/$folders[2]/$folders[3]/$folders[4]/$value";
system("cp -r $dir1 $dir2");
}
}
}
}
This works. It looks like part of my problem from before was that I was trying to run this on a directory in my D: drive--when I moved it to the C: drive, it worked fine without any permissions errors or anything. I did try to implement something with Path::Tiny, but this script was so close to being functional (and it was functional in a Unix environment), that I decided to just complete it.
You really should read the Path::Tiny doccu. It probably contains everything you need.
Some starting points, without error handling and so on...
use strict;
use warnings;
use Path::Tiny;
my $start=path('D:/DATA/DATA_x');
my $iter = path($start)->iterator({recurse => 1});
while ( $curr = $iter->() ) {
#select here the needed files - add more conditions if need
next if $curr->is_dir; #skip directories
next if $curr =~ m/HHH.*\.{2,3}txt$/; #skip ...?txt
#say "$curr";
my $content = $curr->slurp;
if( $content =~ m/some_pattern/ ) {
#do something wih the file
say "doing something with $curr";
my $newfilename = path("insert what you need here"); #create the needed new path for the file ..
path($newfilename->dirname)->mkpath; #make directories
$curr->move($newfilename); #move the file
}
}
Are you sure of the directory path you are trying to create. The mkdir call might be failing if some of the intermediate directories doesn't exist. If your code is robust to ensure that
the variable $new_over_dir contains the directory path you have to create, you can use method make_path from perl module File::Path to create the new directory, instead of 'mkdir'.
From the documentation of make_path:
The make_path function creates the given directories if they don't
exists before, much like the Unix command mkdir -p.

Search directory and store in hash

From user's input, I should able to search its sub directories until the fixed directory which named 'list'. The question is how to store the path into hash so that I could retrieve the data and display it afterwards.
my directory tree:
->blue -->Aug21 --->projA ---->list ----->name
/tmp/general/place/brand->red -->Jan03
->yellow -->June22 --->projB ---->list
directory.pl:
use strict;
use warnings;
print "Searching Directory...\n";
&search_dir('/tmp/general/place/brand'); #could be 'tmp/general/place/brand/blue/Aug21'
my ($input,$tt,$str,$fn,#pp,#cap);
sub search_dir{
$input=shift;
opendir DH,$input or die"$!";
while($_=readdir(DH)){
next if $_ eq "." or $_ eq "..";
if ($_ eq "list"){
$fn = $input.'/'.$_;
push #pp, $fn;
return;
}
else{
$fn = $input.'/'.$_;
}
if(-d $fn){
push #cap,$fn;
}
}
if(scalar #cap == 0){
return;
}
foreach (#cap){
&search_dir($_) ;
}
}
if (#pp){
print "Located directory...\n";
foreach $tt (#pp){
$str='/tmp/general/place/brand';
$tt=~ s/$str//g;
print $tt,"\n";
#Hash? $file{$color}{$date}{$quantity}{$list}= split (/\//,$tt);
}
}
else {
print "Could not locate directory\n";
}
Expected Output:
Searching Directory...
Located Directory...
/blue/Aug21/projA/list
/yellow/June22/projB/list
Info :1
Color :blue
Date :Aug21
Project :projA
Info :2
Color :yellow
Date :June22
Project:projB
Under linux it maybe easier to use find. Quick and dirty but it will do the job.
my #dirs = `/usr/bin/find -name "/tmp/general/place/brand/" -type d`;
chomp(#dirs);
my $data = {};
foreach my $dir (#dirs){
my #path = split(/\//,$dir);
#you have found a list in the right depth
if (scalar(#path) >= 7 && $path[7] eq 'list'){
$dir =~ s!/tmp/general/place/brand!!is;
print "Found $dir\n";
print "Color :".$path[4];
...
}
}

how to read files and its subdirectories files using perl

in Perl i need to read file from a parent directory to it's last file it any sub directory is there i need to read those files too!so I've tried something like this with the help of recursive function but it gives infinite loop so can anybody help me!
code;
sub fileProcess{
(my $file_name)=#_;
print "$file_name it is file\n";
}
sub main{
(my $dir)=#_;
chdir $dir;
my $tmp=`pwd`;
my #tmp =<*>;
chomp(#tmp);
foreach my $item(#tmp){
chomp($item);
if(-d $item){
dirProcess("$tmp/$item");
}else{
fileProcess($item);
}
}
}
sub dirProcess{
(my $file_name)=#_;
print ">>the corresponding dir is $file_name<<";
main($file_name);
}
my $home="../../Desktop";
chdir $home;
my $path=`pwd`;
main($home);
Here's a sub that will search recursively :
sub find_files {
my ($dir) = #_;
my (#files, #dirs) = ();
my (#allfiles, #alldirs) = ();
opendir my $dir_handle, $dir or die $!;
while( defined( my $ent = readdir $dir_handle ) ) {
next if $ent =~ /^\.\.?$/;
if( -f "$dir/$ent" ) {
push #files, "$dir/$ent";
} elsif( -d "$dir/$ent" ) {
push #dirs, "$dir/$ent";
}
}
close $dir_handle;
push #allfiles, #{ process_files($_) } for #files;
push #alldirs, #{ find_files($_) } for #dirs;
return \#alldirs;
}
The main reason your code isn't working is that, when dirProcess it calls main again which does chdir to a different directory. That means the rest of the files in the #tmp array aren't found.
To fix it I have just added a chdir $dir after the call to dirProcess. In addition I have
Added use strict and use warnings. Yyou must always put these at the top of your program.
Removed all calls to pwd which were unnecessary. You know what you present working directory is because you've just set it!
Removed unnecessary chomp calls. The information from glob never has trailing newlines. The one string that did need chomping is $tmp but you didn't do it!
It's still not a very nice piece of code, but it works!
use strict;
use warnings;
sub fileProcess {
(my $file_name) = #_;
print "$file_name it is file\n";
}
sub main {
(my $dir) = #_;
chdir $dir;
my #tmp = <*>;
foreach my $item (#tmp) {
if (-d $item) {
dirProcess("$dir/$item");
chdir $dir;
}
else {
fileProcess($item);
}
}
}
sub dirProcess {
(my $file_name) = #_;
print ">>the corresponding dir is $file_name<<\n";
main($file_name);
}
my $home = "../../Desktop";
main($home);