Find a String in Several Files at a ago

2teez 0 Tallied Votes 241 Views Share

I really got bored and tired of trying to find or remember some codes usage in so many files saved on my system, when it was needed.

So, I called 'Monsieur' perl for help, hacking together some codes which works great for me and solve my problem of boredom.

You run the code from the CLI, like so: perl <perl_script.pl> <-l | -location> <-s | -search >
E.g. perl search_doc.pl -l Desktop/Doc_file -s Win32::OLE

  1. Your perl file : search_doc.pl
  2. Your location : -l Desktop/Doc_file or -location Desktop/Doc_file
  3. Your search string: -s Win32::OLE or -search Win32::OLE

You can decide to use --l or --search as you want.

Please feel free to add comments, to ask question and use as you want.

use warnings;
use strict;
use File::Find;
use Getopt::Long;

GetOptions(
    'location=s' => \my $location,
    'search=s'   => \my $word_to_search,
);

# the location of the script is used as default
# if no location is specified

$location //= '.';

# the script dies if there is nothing to search
die 'No word to search ...', unless defined $word_to_search;

find(
    sub {

        if ( -e && -f ) {
            my $status = 0;
            open my $fh, '<', $_ or die $!;
            while ( my $line = <$fh> ) {
                ++$status && last if $line =~ /$word_to_search/i;
            }
            print $File::Find::name, $/ if $status == 1;
        }

    },
    $location
);
rsprice 0 Unverified User

I cannot get this to execute without errors. I cut-and-pasted the script into a UNIX text file. You can see my test run below:

DEV-TEST: </home/rp3138/perl [1072]>
rp3138@fstst05: perl search_doc.pl -l /home/rp3138/.profile -s "alias"
Scalar found where operator expected at search_doc.pl line 26, near "++$status && last if $line =~ /$word_to_search"
  (Might be a runaway multi-line // string starting on line 14)
        (Missing operator before $word_to_search?)
Global symbol "$status" requires explicit package name at search_doc.pl line 14.
Global symbol "$fh" requires explicit package name at search_doc.pl line 14.
Global symbol "$line" requires explicit package name at search_doc.pl line 14.
Global symbol "$fh" requires explicit package name at search_doc.pl line 14.
Global symbol "$status" requires explicit package name at search_doc.pl line 14.
Global symbol "$line" requires explicit package name at search_doc.pl line 14.
syntax error at search_doc.pl line 26, near "++$status && last if $line =~ /$word_to_search"
Unmatched right curly bracket at search_doc.pl line 27, at end of line
Execution of search_doc.pl aborted due to compilation errors.
2teez 43 Posting Whiz

Hi rsprice,
Thanks for checking this code snippets out. What VERSION of perl are you using?
This code should run on perl-5.12.4 upwards, but NOT on 5.8.1 downwards, because of line 14.

I have not tested it on any version between 5.8.1 and 5.12.4.

Really, maybe it's time to upgrade the perl version on your system. Or use prelbrew if that is not possible. So that you can switch from one version to another as you want.

However, if all that is not possible, you can MODIFY the line 14 of the Code Snippets above like so:

$location //= '.';

to

$location ||= '.';

And it should work well even on version 5.6.
I hope this help. long to hear from you again. Thank you

rsprice 0 Unverified User

Hello 2teez:

Your suggested change of // to || worked fine.
Now the script works correctly in two Perl versions I have:
v5.8.4
v5.8.7

Thanks!

2teez 43 Posting Whiz

Nice one...
But if I may suggest, v.5.8 of Perl is too old.
If you can upgrade to like v.5.12 or v.5.14.
Or use perlbrew since you are in unix environment....

Thanks again...

rsprice 0 Unverified User

2teez:

Thanks for your suggestion to upgrade Perl. I cannot do that on the system where I have been testing your script because a legacy application from the 20th century requires an old Perl version.

I will try this later on my own Mac computers with newer Perl versions.

rsprice 0 Unverified User

2teez:

I just tested this script on my iMac running Perl v5.12.4. It works fine in the original form or with the revised version.

2teez 43 Posting Whiz

Cool that is the Spirit.....Thank you

rsprice 0 Unverified User

I decided to add a couple of flags to control the outputs. These do not take values. The -i flag means "Ignore Case". If this option is used, the search string "Steve" would match "steve" or "Steve" or "sTeve" etc. The -f flag means "File Only". If -f is used only the name of the file where the match was found would be printed. Otherwise, the file name + ": " + the line with the match will be printed.

#   05/31/2013  Added -i and -f flags; -i: ignore case; -f file only display
#               The new default behavior is to display the line of a match;
#               If -f is given, only the name of the file will be displayed.
#------------------------------------------------------------------------------- 
use warnings;
use strict;
use File::Find;
use Getopt::Long;

GetOptions(
    'file_only' => \my $file_only,
    'ignore_case' => \my $ignore_case,
    'location=s' => \my $location,
    'search=s'   => \my $word_to_search,
);

if (!defined($ignore_case)) {
    $ignore_case = 0;
}

if (!defined($file_only)) {
    $file_only = 0;
}

# the location of the script is used as default
# if no location is specified

# NOTE: The original code used //. This requires Perl 5.12.4 or greater:
#$location //= '.';
$location ||= '.';  # This change works with Perl 5.8.4 and greater.

# the script dies if there is nothing to search
die 'No word to search ...', unless defined $word_to_search;

$::show_line='';

find(
    sub {
        if ( -e && -f ) {
            my $status = 0;
            open my $fh, '<', $_ or die $!;
            while ( my $line = <$fh> ) {
                $::show_line=$line;
                if ($ignore_case) {
                        ++$status && last if $line =~ /$word_to_search/i;
                    } else {
                        ++$status && last if $line =~ /$word_to_search/;
                }
            }

            if ($status == 1) {
                print $File::Find::name;
                if ($file_only) {
                    print "\n";
                } else {
                    print ":\t$::show_line\n";
                }
            }
        }
    },
    $location
);
rsprice 0 Unverified User

Next I want to change this so that you can give a pattern for file names and the script will look in all files that match. Such as "*.c" and then all C files will be searched.

2teez 43 Posting Whiz

Hi,
Please, I will look into the code you posted say in the next 24 hours or so. Because am presently hooked up in some others stuff. But I need say this.
This Original script search directories reading through any and ONLY files to find word that was indicated from the CLI. The case of the word to search is also ignored by default.

If I get want you are trying to do, I think the script already does them except for indicating the line, where the matched string occur.

Long to read from you,
2teez

rsprice 0 Unverified User

I will look at this next week. Logging out now for the weekened. I was not able to get the original script to work on patterns like "-l *.pl" to look in a set of Perl scripts. I did not try it with a subdirectory for -l target.

Also, I want to make it show all the matches in a given file, not just the first one. THe original code stops looking after the first match.

rsprice 0 Unverified User

2teez:
You are correct that the original script does look at all the files in the folder that you indicate with the -l location CLI argument. I want to also allow the -l location to specify a regular expression for filenames to match. An example: -l /tmp/myProject*.txt. THis would look in files that match, such as /tmp/myProject01.txt, /tmp/myProject02.txt, /tmp/myProjectSecret.txt, etc.

Your original script never shows the matchching line(s) found, and it stops looking after the first match, and it is hard coded to ignore case while searching for the word to match. So I added the -i and the -f flags.

Steve

2teez 43 Posting Whiz

Hi Steve,

like I promised the other day... I looked into the your code and evetually I decided to make this a Module. Which already I have written but the documentation of what and how to use is not finished yet.

The module does what this script here is doing, then does what you wanted and then again export by default a subroutine that behaves like the Unix find command.

By tomorrow even if am not done with the module, I will send a copy to you and indicate how it would be used. So that you can test it and see if it works for you, then see what you can break :)

All you have to do using the module would be something like:

use String::Search qw(get_file_name);
my ( $word, $directory ) = ( 'perl', '.');
get_file_name( $word, $directory );

#OR 

use String::Search qw(get_line_name);
my ( $word, $locate ) = ('perl', "*.pm");
get_line_name( $word, $locate );

#OR

use String::Search;
find_file_name(\@ARGV);

The find_file_name is the fastest of all and it loads by default, get_line_name functions like what you wanted and it faster than the get_file_name and both has to be loaded explicitly by the user.

That is all your script is. Just those three or two lines.

Thanks a lot. I really enjoy reading from you.

rsprice 0 Unverified User

Cool. I will look at this tommorrow.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.