Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi everyone. I have a question with regards to a Perl program that I have written for Exercise 2.1 of the book Learn Perl the Hard Way. The question goes like:

Write a program called grep.pl that takes a pattern and a list of files as command line arguments, and that traverses each file printing lines that match the pattern.

I have 4 files of which their contents are listed below: file_list.txt --> contains a list of files: "a.txt", "b.txt", "c.txt" a.txt --> Contains the word "I" b.txt --> Contains the words "am going to" c.txt --> Contains the word "school"

I have tried to write the program below, but I don't understand why it only prints the contents from a.txt, i.e. it only prints the word "I". I inserted print functions along the way and I realized that the subroutine "open_file" is called only once but I don't understand why this happens.

May I ask if anyone can give a hint on resolving this?

Thank you!

#!/usr/bin/perl use strict; use warnings; sub open_file { my $file = scalar(@_); foreach my $temp (@_) { open FILE, $temp; while (my $line = <FILE>) { print $line; } } } sub grep_file { my $file = shift; open FILE, $file; while (my $line = <FILE>) { open_file ($line); } } grep_file @ARGV

Replies are listed 'Best First'.
Re: Question on File Handler and Subroutines in Perl
by NetWallah (Canon) on Mar 05, 2019 at 03:21 UTC
    You have several problems:

    1. Your main call should be:

    grep_file($_) for @ARGV; # ===
    Update:Corrected per tobyink's recommendation, below.

    This will call the grep for each file name.

    2. Your "open file" should really be "Process_record" -- to process each record of each file.
    The file is already open and read, before that is called.

    3. I don't see any code to search through the file(s).

                    As a computer, I find your faith in technology amusing.

      Hi @NetWallah,

      Thank you so much for your patience in responding to my question.

      I have not tried to perform the pattern search; I thought of trying it later as I wasn't able to get the basics correct.

      Actually in the program above, I was thinking of the following:

      1. Read in file_list.txt which contains a list of files: a.txt, b.txt, c.txt.

      2. Open file_list.txt, and then open the individual files listed in file_list.txt.

      Thank you very much for your kind help! :)
Re: Question on File Handler and Subroutines in Perl
by Marshall (Canon) on Mar 05, 2019 at 06:56 UTC
    Start with getting the information you need from the command line.
    Space separated tokens from the command line are in the predeclared array @ARGV.
    A multi-word pattern needs to have " or ' quotes depending upon OS.

    #!/usr/bin/perl use strict; use warnings; my ($pattern, @files) = @ARGV; print "pattern=$pattern files are:@files\n"; __END__ example: >mygrep.pl "some pattern" filea fileb filec pattern=some pattern files are:filea fileb filec
    I would suggest starting with a more simple subset of the problem.
    Write a program that opens FileA.txt and then prints all lines that contain "asdf".
    Create FileA.txt with test cases as you wish.

      Hi Marshall,

      Thank you for your help on this! :)

      I have tried to do what you have told me, i.e.

      Write a program that opens FileA.txt and then prints all lines that contain "asdf". Create FileA.txt with test cases as you wish.

      My program is seen below:
      #!/usr/bin/perl use strict; use warnings; sub open_file { my $file = shift; my $pattern = "asdf"; open FILE, $file; while (my $line = <FILE>) { if ($line =~ m/$pattern/){ print $line; } } } open_file@ARGV

      Hence in the command line (Terminal), I did a: ./open_file.pl FileA.txt, and the printouts were correct.

        Hi NetWallah, Marshall and members of the PerlMonk community,

        Thank you all for your kind help and support. I have managed to solve the question, thanks to an inspiration by Marshall.

        My code is shown below:

        #!/usr/bin/perl use strict; use warnings; sub grep_file { my @files = @_; my $pattern = "asdf"; foreach my $file (@files) { open FILE, $file; while (my $line = <FILE>) { if ($line =~ /$pattern/) { print $line; } } } } grep_file @ARGV
        Thank you all once again! (:
Re: Question on File Handler and Subroutines in Perl
by thanos1983 (Parson) on Mar 05, 2019 at 09:43 UTC

    Hello Anonymous Monk,

    Perl is a well known scripting language that people choose to create 'Modules' that can assist you / us to reduce our lines of code to minimum. For example reading and loading all lines of a file in an array while removing the new line characters all in one line :)

    One of the modules that I usually prefer to use is IO::All.

    Then your task is simplified you just need to set the pattern to match (string or even multiple strings) and grep all elements that match your pattern using grep as you have done already :).

    Small sample of code bellow:

    #!/usr/bin/perl use strict; use warnings; use IO::All; use Data::Dumper; foreach my $file (@ARGV) { if ( -e $file ) { # double check existence my @lines = io($file)->chomp->slurp; # Chomp as you slurp if (my @matches = grep( /string/, @lines ) ) { print "Found in File: '$file':\n"; print Dumper \@matches; } } } __END__ $ perl test.pl a.txt b.txt c.txt Found in File: 'a.txt': $VAR1 = [ 'Line two interested, string' ]; Found in File: 'b.txt': $VAR1 = [ 'Line one interested, string', 'Line two interested, string' ]; __DATA__ $ cat a.txt b.txt c.txt Line one not interested Line two interested, string Line one interested, string Line two interested, string Line one not interested Line two not interested Line three not interested

    You need to adjust it to your preferences but it should be close to what you want to achieve.

    Update: I do not want to include the solution with the subroutine. There are several ways to put all your data together. One possible way I would suggest push. Give it a try and we will help you more if you get problems. After all programming / scripting is all about trying and failing until you get it right :)

    Hope this helps, BR.

    Seeking for Perl wisdom...on the process of learning...not there...yet!

      Hi thanos1983,

      Thank you for your detailed reply! (:

      I will take a look at your suggested solution. :) One thing I am still a bit puzzled about, is how to open a file that contains the names of the files in the current directory, and then iterate through them to print out lines that matches the pattern I want. I am currently still working on that.

      Thank you once again!

        Hello again Anonymous Monk,

        You are welcome :)

        Update: Sorry I got your last question completely wrong :). You can repeat the same procedure as you open the files and instead of @ARGV use @files.

        Sample of code:

        #!/usr/bin/perl use strict; use warnings; use IO::All; use Data::Dumper; my @files = io('fileName.txt')->chomp->slurp; print Dumper \@files; __END__ $ perl test.pl $VAR1 = [ 'a.txt', 'b.txt', 'c.txt' ];

        Update2: I think I finally understood what you mean. You want to provide a file as a parameter to the script that contains the names of the files that you want to search through for the keyword. If this is the case see bellow:

        #!/usr/bin/perl use strict; use warnings; use IO::All; use Data::Dumper; sub grepFileFromSubroutine { my @files = io(shift)->chomp->slurp; my @final; foreach my $file (@files) { if ( -e $file ) { # double check existence my @lines = io($file)->chomp->slurp; # Chomp as you slurp if (my @matches = grep( /string/, @lines ) ) { push @final, "Found in File: '$file':", @matches; } } } return \@final; } print Dumper grepFileFromSubroutine(@ARGV); __END__ $ perl test.pl fileName.txt $VAR1 = [ 'Found in File: \'a.txt\':', 'Line two interested, string', 'Found in File: \'b.txt\':', 'Line one interested, string', 'Line two interested, string' ]; __DATA__ $ cat a.txt b.txt c.txt Line one not interested Line two interested, string Line one interested, string Line two interested, string Line one not interested Line two not interested Line three not interested

        If still is not what you want provide us more information to assist you more :)

        Regarding your last question read this past question which contains also examples and a variety of solutions (Find filename that has the pattern in a directory).

        I usually prefer to use File::Find::Rule. A previously asked question with a sample of code with this module see (Re: Capturing and then opening multiple files).

        Hope this helps, BR.

        Seeking for Perl wisdom...on the process of learning...not there...yet!
Re: Question on File Handler and Subroutines in Perl
by tobyink (Canon) on Mar 05, 2019 at 12:52 UTC

    If you look at this:

    while (my $line = <FILE>) {

    You'll find that $line is now "a.txt\n". It keeps the line break at the end of the line. Look at the chomp function to resolve that.

    Also the following line isn't even doing anything:

    my $file = scalar(@_);

      Hi tobyink,

      Ah yes, you are right. It keeps getting stuck at the file a.txt and printing only the contents out from it. :( Now I know why. It's because of the "\n". I will read up what chomp does. Thank you for the hint! :) The book "Learn Perl the Hard Way" really lives up to its name. Haha. Oh dear.

      Ah yes, I realized that "my $file = scalar(@_)" isn't doing anything too after debugging it yesterday. Thank you for pointing it out. Just wondering, does that statement convert an array "stream" into a scalar quantity, e.g. a string?

      Thank you (:

        Just wondering, does that statement convert an array "stream" into a scalar quantity, e.g. a string?

        Yes, but not the scalar quantity you might imagine. Running scalar on an array or list argument returns the number of elements in the array or list as this is what happens when an array a list is evaluated in scalar context. Conversely, if you wanted a string which was a concatenation of all the elements in the array then you might use join.

        #!/usr/bin/env perl use strict; use warnings; my @foo = ('dog', 'bites', 'man', 'bites', 'dog'); printf "scalar = '%s'\n", scalar @foo; printf "join = '%s'\n", join ' ', @foo;

        Update: removed erroneous mentions of lists as their behaviour in scalar context differs from arrays (returns the last item). Thanks choroba for reminding me of this.

Re: Question on File Handler and Subroutines in Perl
by karlgoethebier (Abbot) on Mar 05, 2019 at 13:06 UTC

    You may read perlop for the null filehandle <> and a poor man‘s grep. Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

      Hi karlgoethebier,

      Thank you for your suggestion!

      I will read the perlop (:

      Thank you! (:

        See also

        «The Crux of the Biscuit is the Apostrophe»

        perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

      Hi karlgoethebier,

      Thank you for your suggestion!

      I will read the perlop (:

      Thank you! (:

Re: Question on File Handler and Subroutines in Perl
by tybalt89 (Monsignor) on Mar 06, 2019 at 03:53 UTC
    #!/usr/bin/perl # https://perlmonks.org/?node_id=1230880 use strict; use warnings; my $pattern = shift; chomp( @ARGV = <> ); /$pattern/ and print while <>;