Find file that does not contain specific string - perl

I want to find file that does not contain the specific string?
The listed file is like below
../../../experiment/fileA.txt (contain word 'Book')
../../../experiment/fileB.txt (contain word 'Book')
../../../experiment/fileC.txt (do not contain word 'Book')
../../../experiment/fileD.txt (contain word 'Book')
Here is my code
use strict;
use warning;
my $dirname = "../../../experiment/";
my $keyword = "Book";
my #result;
my $find_file = sub {
my $F = $File::Find::name;
if ($F =~ /txt$/) {
open my $in, "<", $F or die $!;
while(<$in>) {
if (/\Q$keyword\E/){
next;
}else{
push #result, $F;
return;
}
}
}
};
find ({ wanted => $find_file, no_chdir=>1}, $dirname );
foreach my $result (#result){
chomp $result;
$result =~ s{.*/}{};
print "$result\n";
}
But it seem does not work. It display all file whether it has the $keyword or not. I only want it to display only if the file does not have the $keyword

There's a simple logic error. The code goes through lines of each file
while (<$in>) {
if (/\Q$keyword\E/){
next;
} else {
push #result, $F;
return;
}
}
and as soon as any one line doesn't have $keyword it adds the file to #result.
You need to check all lines and if $keyword is never found only then add a file. The easiest way to do this is to return from the sub as soon as the thing is found
while (<$in>) {
return if /\Q$keyword/;
}
push #result, $F;

This doesn't address your code, but I'd like to point out that with the grep command on any Linux system you can do exactly what it looks like you're trying to do with this command:
grep -L Book -R ../../../experiment/

Path::Iterator::Rule makes tasks like this really simple. As a side note, I would recommend resolving the directory to an absolute path before iterating.
use strict;
use warnings;
use Cwd 'abs_path';
use File::Basename;
use Path::Iterator::Rule;
my $dirname = abs_path "../../../experiment/";
my $keyword = "Book";
my $rule = Path::Iterator::Rule->new->not_dir->name(qr/txt$/)->not_line_match(qr/\Q$keyword\E/);
my $next = $rule->iter($dirname);
while (defined(my $file = $next->())) {
print basename($file), "\n";
}

Related

Can't find a string in array

I have a file with almost 1,500 names of Marvel heroes, each name in new line. I have to ask user what his favourite hero is and find out if it's a hero from the list or not. Here's what I have right now. It doesn't work: I can guess only the last hero from the list. For the rest it just prints that they are not on the list.
print "Whats your favourite hero?\n";
my $hero = <stdin>;
chomp $hero;
open FILE, "<list_marvel.txt";
my #marvel = <FILE>;
chomp(#marvel);
my $result = 0;
foreach (#marvel) {
if ($_ eq $hero);
}
if ($result == 1) {
print "That hero is on the list";
}
else {
print "$hero is not on the list.\n";
}
Here are two files:
-Perl code : Perl Code
-List of heroes : List
Your program has a syntax error and won't compile. It certainly won't find only the last name on the list
The main problem is that you never set $result, and if($_ eq $hero) should be something like $result = 1 if($_ eq $hero)
You must always use strict and use warnings at the top of every Perl program you write. It is an enormous help in finding straighforward problems
Here's a working version
use strict;
use warnings;
my $filename = 'list_marvel.txt';
open my $fh, '<', $filename or die qq{Unable to open "'list_marvel.txt'": $!};
print "Whats your favourite hero? ";
my $hero = <>;
chomp $hero;
my $found;
while ( <$fh> ) {
chomp;
if ( $_ eq $hero ) {
++$found;
last;
}
}
print $found ? "$hero is on the list\n" : "$hero is not on the list";
You don't set $result anywhere to true.
Make your foreach loop like this:
foreach(#marvel){
$result = $_ eq $hero;
}
or
foreach (#marvel){
$result = 1 if $_ eq $hero
}
You forgot to increment your $result. If you indent your code properly, it is easier to see.
foreach (#marvel) {
# here something is missing
if ( $_ eq $hero );
}
Add $result++ if $_ eq $hero; in the foreach.
You should always use strict and use warnings. That would have told you about a syntax error near );.
Also consider using the three argument open with lexical filehandles.
Rewritten it looks like this:
use strict;
use warnings;
use feature 'say'; # gives you say, which is print with a newline at the end
say "What's you favourite hero?";
my $hero = <STDIN>;
chomp $hero;
# alsways name variables so it's clear what they are for
my $found = 0;
# die with the reason of error if something goes wrong
open my $fh, '<', 'list_marvel.txt' or die $!;
# read the file line by line
while ( my $line = <$fh> ) {
chomp $line;
if ( $line eq $hero ) {
# increment so we know we 'found' the hero in the list
$found++;
# stop reading at the first hit
last;
}
}
close $fh;
# no need to check for 1, truth is enough
if ( $result ) {
say "That hero is on the list.";
}
else {
say "$hero is not on the list.";
}
First, you miss setting the $result at around if($_ eq $hero).
Then, you may wish to make you comparison case insensitive. This would require a regular expression, e.g.:
$result = 1 if (/^$hero$/i);
Just modified your code. After if condition increment $result. Always use use strict and use warnings and always use 3 arguments to open a file.
use strict;
use warnings;
print "Whats your favourite hero?\n";
my $hero = <stdin>;
chomp $hero;
open FILE, "<", "list_marvel.txt" or die $!;
chomp (my #marvel = <FILE>);
close FILE;
my $result = 0;
foreach my $name (#marvel)
{
if($name eq $hero)
{
$result++;
}
}
if ($result == 1)
{
print "That hero is in the list.\n";
}
else
{
print "$hero is not in the list.\n";
}
This will take a single user entry from STDIN. It will run through the file of hero names, and if one matches the user entry it will print the name and exit the loop. If the name is not found it will tell you:
use warnings;
use strict;
open my $file1, '<', 'input.txt' or die $!;
print "Enter hero: ";
chomp(my $hero = <STDIN>);
my $result = 0;
while(<$file1>){
chomp;
if (/$hero/){
print "$_\n";
$result++;
last;
}
}
print "hero not in list\n" if $result == 0;

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.

Can i collect the output of find(\&wanted, #directories) in an array

I am writing a script which will traverse the directory(including subdir also) and push the desired file in an array so that i can work on each file.
Here is my code:
use strict;
use warnings;
use File::Find;
my $path = $ARGV[0];
find({ wanted => \&GetappropriateFile }, $path);
sub GetappropriateFile
{
my $file = $_;
my #all_file;
# print "$file\n";
if ( -f and /traces[_d+]/)
{
#print "$file\n";
open(my $fh, "<", $file) or die "cannot open file:$!\n";
while( my $line = <$fh>){
$line =~ /Cmd\sline:\s+com.android*/;
push(#all_file,$file);
#print "$file\n";
}
close($fh);
#print"#all_file\n";
}
}
Problem Area : my $file = $_;
Instead of using " $file" if i could get a way to use an array here then i can easily read those files one by one and filter it.
Here what i am tring to do is : I have to open each file and check for the string "Cmd line: com.android" as soon as i get this string in the file i have to push this current file in an array and start reading the another file.
It would be better to avoid global vars.
use strict;
use warnings;
use File::Find qw( find );
sub IsAppropriateFile {
my ($file) = #_;
if (-f $file && $file =~ /traces[_d+]/) {
open(my $fh, "<", $file) or die "cannot open file:$!\n";
while ( my $line = <$fh> ) {
if ($line =~ /Cmd\sline:\s+com.android*/) {
return 1;
}
}
}
return 0;
}
{
my $path = $ARGV[0];
my #matching_files;
find({
wanted => sub {
push #matching_files, $_ if IsAppropriateFile($_);
},
}, $path);
print("$_\n") for #matching_files; # Or whatever.
}
Put declaration of #all_file outside of function, and use it after find() finishes,
my #all_file;
sub GetappropriateFile
{
..
}
You could also stop with file reading after successful match,
if ($line =~ /Cmd\sline:\s+com.android*/) {
push(#all_file, $file);
last;
}

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);

Perl opening files from recursive directory

So my program is supposed to recursively go through a directory and then for each file in the directory, open up the file and search for the words "error" "fail" and "failed." Then it should write the instances where these words occur, as well as the rest of the characters on the line after those words, out to a file designated in the command prompt. I have been having some trouble making sure the program performs the search on the files that are found in the directory. Right now it does recurse through the directory and even creates a file to write out to, however, it does not seem to be searching through the files found in the recursing. Here is my code:
#!/usr/local/bin/perl
use warnings;
use strict;
use File::Find;
my $argument2 = $ARGV[0];
my $dir = "c:/program/Scripts/Directory1"; #directory to search through
open FILE, ">>$argument2" or die $!; #file to write out
my $unsuccessful = 0;
my #errors = ();
my #failures= ();
my #failures2 = ();
my #name = ();
my #file;
my $file;
my $filename;
opendir(DIR, $dir) or die $!;
while($file = readdir(DIR)){
next if($file =~ m/^\./);
foreach(0..$#file){
print $_;
open(FILELIST, '<', $_);
while(<FILELIST>){
if (/Unsuccessful/i){
$unsuccessful = 1;
}
if(/ERROR/ ){
push(#errors, "ERROR in line $.\n");
print "\t\tERROR in line $.:$1\n" if (/Error\s+(.+)/);
}
if(/fail/i ){
push(#failures, "ERROR in line $.\n");
print FILE "ERROR in line $.:$1\n" if (/fail\s+(.+)/);
}
if(/failed/i ){
push(#failures2, "ERROR in line $.\n");
print FILE "ERROR in line $.:$1\n" if (/failed\s+(.+)/);
}
if ($unsuccessful){
}
}
close FILELIST;
}
}
closedir(DIR);
close FILE;
So, to clarify, my problem is that the search contained in the "while()" loop does not seem to be executing on the files found in the directory recursively. Any comments/suggestions/help that you can give on why this may be happening would be very helpful. I am new to Perl so some sample code would also just help me understand what you are trying to say. Thank you very much.
Typically, when I want to do something on recursive files, I start with find2perl . -print which generates the boilerplate for me with the wanted function from which I can modify to do whatever I want.
For example
# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '.');
exit;
sub wanted {
return unless -f $File::Find::name;
return unless -R $File::Find::name;
open (F,"<",$File::Find::name) or warn("Error opening $File::Find::name : $!\n");
while(<F>) {
if(m/error/) { print; }
if(m/fail/) { print; }
}
}
This is an example of a recursive perl directory listing. In reality, I would probably use file::find, or really just grep -R, but I am assuming this is homework of some kind:
use strict;
my $dir = $ARGV[0];
my $level = 0;
depthFirstDirectoryList($dir, $level);
sub depthFirstDirectoryList{
my ($dir, $level) = #_;
opendir (my $ind, $dir) or die "Can't open $dir for reading: $!\n";
while(my $file = readdir($ind)){
if(-d "$dir/$file" && $file ne "." && $file ne ".."){
depthFirstDirectoryList("$dir/$file", $level + 1);
}
else{
no warnings 'uninitialized';
print "\t" x $level . "file: $dir/$file\n";
}
}
}