Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

While loop not printing anything after using grep

by skjeiu (Novice)
on Dec 29, 2020 at 11:22 UTC ( [id://11125925]=perlquestion: print w/replies, xml ) Need Help??

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

Hello all,

In the below script the grep if statement works but the while loop is not printing anything. If I comment the if statement, the while loop works as I would expect it to, that is, printing the content of the file.



#!/bin/perl use strict; use warnings; my $input_file = 'input.txt'; open(my $in_file,'<',$input_file) or die "Can not open file $input_fil +e for reading: $!.\n"; if (grep{/Friday/} $in_file) { print "Match\n"; } else { print "No match\n"; } print "Before\n"; while (<$in_file>) { print; } print "After\n"; close($in_file);


My ultimate gall here is to search for the string 'Friday' in the file 'input.txt', if 'Friday' is missing, add'Friday' to 'input.txt on line 2.



The content of 'input.txt is:
Monday
Sunday

Replies are listed 'Best First'.
Re: While loop not printing anything after using grep (updated)
by haukex (Archbishop) on Dec 29, 2020 at 11:33 UTC

    Welcome to the Monastery, skjeiu. Please note the advice in How do I post a question effectively?: At the very least, please use <code> tags to format your code. Also, you posted this question in the incorrect section; I have moved it to Seekers of Perl Wisdom for you.

    if (grep{/Friday/} $in_file)

    This is unlikely to be the code you're actually running, as this would try to match the pattern against the filehandle stored in the variable, instead of the contents of the file. Instead, I assume your code looks like if (grep{/Friday/} <$in_file>), as that would explain the problem you're having: in list context, the <$filehandle> operator (see I/O Operators in perlop) reads all the lines from the file and returns them, this is what grep is matching against, but once you've read everything from the file, the second <> operator won't return anything.

    There are several possible ways to work around this. For example, if the file will always be small enough to comfortably fit into memory, you could read the contents of the file into an array and then loop over that with grep and foreach as many times as you like. Another approach would be to integrate everything into one while loop, i.e. move the /Friday/ match there. If you could explain more about what your input files look like and what you're actually trying to do, I'm sure we could suggest some approaches fitting to your application.

    Update:

    My ultimate gall here is to search for the string 'Friday' in the file 'input.txt', if 'Friday' is missing, add'Friday' to 'input.txt on line 2. The content of 'input.txt is: Monday Sunday

    Sorry, I must have missed this on the first read because of the formatting (unless you edited your node? please mark your updates when editing). Anyway, please also use <code> tags for sample input and output. Since you haven't shown your expected output, it's a little hard to guess what you mean - do you want the contents of the second line to be replaced, the string "Friday" to be appended, or do you want a new line to be inserted? And does the length of the input file matter at all?

    Anyway, your approach is a good start. Assuming your files aren't too long, then one way to do what you want would be to rewind the input file to the beginning with seek: insert seek $in_file, 0, 0 or die "seek: $!"; after the grep and before the while to start reading the file from the beginning again. You should then be able to use this as the basis to do what you want. (Note that this does have one disadvantage: it doesn't reset the special line counter variable $., in case you were planning to use that in your while loop - it is possible to do this manually via $.=0 right after the seek.)

Re: While loop not printing anything after using grep
by LanX (Saint) on Dec 29, 2020 at 11:33 UTC
    Welcome to the monastery. :)

    Please put <code> </code> tags around your code and data!

    From what I can decipher

    • you are applying the grep outside the while loop.
    • you are testing the file handle not any string

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      First of all, I'd like to apologize for not using the HTML tags. I'm learning HTML together with Perl, so thanks for bearing with me during my learning curve and for the warm welcome.


      With the feedback I've got so far I managed to get the while loop working the way I wanted! :)


      Content of 'input.txt':
      Monday
      Saturday
      Sunday

      #!/bin/perl use strict; use warnings; my $input_file = 'input.txt'; my $output_file = 'output.txt'; open(my $in_file,'<', $input_file) or die "Can not open file $input_fi +le for writing: $!.\n"; open(my $out_file,'>', $output_file) or die "Can not open file $output +_file for writing: $!.\n"; if (grep{/Monday/} $in_file) { } else { seek $in_file, 0, 0; while (<$in_file>) { print $out_file $_; if ($. == 1) { print $out_file "Friday\n"; } } } close($in_file); close($out_file);


      In the above code, I have two issues:
      - Else clause is alwayse executed, why (should only be executed if 'Friday' is not found in 'input.txt')?
      - Empty block in the if statement; how can I avoid this?


      Output of 'output.txt':
      Monday
      Friday
      Saturday
      Sunday


      My ultimate gall is to have input.txt populated with 'Friday' if it is missing. As far as I understand it, in Perl you first need to create a new file with the desired output then rename that file to the original name.
      So in my case that would be with the rename function:

      rename $output_file $input_file;


      If there is a better way to get what I want, I'm open to suggestion.

        See my previous node again:

        Else clause is alwayse executed, why (should only be executed if 'Friday' is not found in 'input.txt')?

        You haven't used the <> operator, as in grep {/Monday/} <$in_file>.

        if ($. == 1) {

        If you use seek, you need to reset $. as I showed.

        Empty block in the if statement; how can I avoid this?

        You can invert the condition in the if, as in if ( !( ... ) ) or if ( not ... ) (just be aware of Operator Precedence and Associativity). Perl also offers the equivalent unless (...) {...}, but IMHO sometimes if (not ... is still more readable than unless (....

        As far as I understand it, in Perl you first need to create a new file with the desired output then rename that file to the original name. So in my case that would be with the rename function

        Yes, that is one way to do it - if you don't mind me plugging my own module, see File::Replace.

        As far as I understand it, in Perl you first need to create a new file with the desired output then rename that file to the original name. So in my case that would be with the rename function ... If there is a better way to get what I want, I'm open to suggestion.

        You are on the right track. For some history and solutions to this fascinating and tricky problem see this old node Re-runnably editing a file in place, especially its "See Also" section.

Re: While loop not printing anything after using grep
by shmem (Chancellor) on Dec 30, 2020 at 00:24 UTC
    My ultimate gall here is to search for the string 'Friday' in the file 'input.txt', if 'Friday' is missing, add'Friday' to 'input.txt on line 2.

    Just do it! But first a code review.

    #!/bin/perl use strict; use warnings; my $input_file = 'input.txt'; open(my $in_file,'<',$input_file) or die "Can not open file $input_fil +e for reading: $!.\n"; if (grep{/Friday/} $in_file) { # WRONG. You are grepping the filehandl +e, # not the content of the file. print "Match\n"; } else { print "No match\n"; } print "Before\n"; # "Before" is long ago. while (<$in_file>) { print; # you don't look for "Friday" looping li +nes. } print "After\n"; # after "After" nothing happens but file + closing. close($in_file);

    A filehandle is a filehandle, not the content of the file. In the while-loop you read the file line-wise, but you don't do anything but printing what you've read. Also, you don't look for line number 2. The line number read is stored in the perl special variable $. which you should test, if you want to add (append?) the string "Friday" to line two. Fix:

    #!/bin/perl use strict; use warnings; my $input_file = 'input.txt'; open(my $in_file,'<',$input_file) or die "Can not open file $input_fil +e for reading: $!.\n"; while (<$in_file>) { if (/Friday/) { warn "match on line $.\n"; # warn goes to STDERR } elsif ($. == 2) { # no match on line 2 chomp; # remove line ending $_ .= "Friday\n"; # add "Friday" and line ending warn "added 'Friday' to line $.\n"; } print; } close($in_file);

    If you want inplace edit, you can place the directives for that after the first line of the script and omit the file opening and the while()-loop as well as the print statement. See perlrun. Read that.
    This edits your 'input.txt' file in-place, moving the original to 'input.txt.bak' first:

    #!/bin/perl -l -pi.bak use strict; use warnings; if (/Friday/) { warn "match on line $.\n"; # warn goes to STDERR } elsif ($. == 2) { # no match but on line 2 $_ .= "Friday"; # add "Friday" (and line endin +g via -l) warn "added 'Friday' to line $.\n"; # warn doesn't add line ending + via -l }

    Hope that helps (HTH).

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'


      Following the constructive feedback here is the working code:

      #!/bin/perl use strict; use warnings; my $input_file = 'input.txt'; my $output_file = 'output.txt'; open(my $in_file,'<', $input_file) or die "Can not open file $input_fi +le for writing: $!.\n"; open(my $out_file,'>', $output_file) or die "Can not open file $output +_file for writing: $!.\n"; if (not (grep{/Monday/} <$in_file>) { seek $in_file, 0, 0; $. = 0; while (<$in_file>) { print $out_file $_; if ($. == 1) { print $out_file "Friday\n"; } } } close($in_file); close($out_file);


      The same code but with 'unless' instead of 'if (not)'

      #!/bin/perl use strict; use warnings; my $input_file = 'input.txt'; my $output_file = 'output.txt'; open(my $in_file,'<', $input_file) or die "Can not open file $input_fi +le for writing: $!.\n"; open(my $out_file,'>', $output_file) or die "Can not open file $output +_file for writing: $!.\n"; unless (grep{/Monday/} <$in_file>) { seek $in_file, 0, 0; $. = 0; while (<$in_file>) { print $out_file $_; if ($. == 1) { print $out_file "Friday\n"; } } } close($in_file); close($out_file);


      As 'seek' could probably be avoided if I rethink things in a different way, I'll get back to this when I have clarified my thoughts


      I appriciated

        open(my $in_file,'<', ...) or die "Can not open ... for writing: $!.\n";

        <nit>
        $in_file is being opened for reading ('<'), not writing.
        </nit>


        Give a man a fish:  <%-{-{-{-<

Re: While loop not printing anything after using grep
by Marshall (Canon) on Dec 29, 2020 at 23:07 UTC
    Here is a different approach for you that might meet your requirements. It looks like you have a file with Day of Week names, sorted in European order (Monday first). In the US, Sunday is the first day of the week. You want to add Friday if it is not already there.

    I would open your input file, read it into a hash. Make sure that Friday is in the hash (whether it was in the input file or not). Sort the hash according the European sort order and print to a file. The safest way to do this is to output to a temp file. Then delete original file. Then rename temp file to the original file name. The purpose of that is to make sure that the data is not "lost" if program crashes while updating its input file.

    Below, I use the already opened DATA file handle and just print to stdout. I think you know how to open files for reading or writing - so I leave that part to you. The code does do some "unnecessary" work if Friday is already there. But the flow is simple with no "if" statements.

    use strict; use warnings; my %day_of_week_order = ( Monday =>1, Tuesday => 2, Wednesday =>3, Thursday => 4, Friday => 5, Saturday => 6, Sunday => 7); my %days = map{chomp; $_=>1}<DATA>; $days{Friday} = 1; # make sure Friday is there foreach my $day (sort{my $A = $day_of_week_order{$a}; my $B = $day_of_week_order{$b}; $A <=> $B } keys %days) { print "$day\n"; } =prints Monday Friday Saturday Sunday =cut __DATA__ Monday Saturday Sunday
    Update: I guess if you want to know if Friday was there or not? The ternary op is good for that, but longer winded code is just fine and will execute just as fast.
    exists ($days{Friday}) ? print "Friday was there\n" : print "Friday wasn't there\n";
    Also, you should be aware that file operations are "expensive" both for CPU and execution time wise. It is rare to seek to the beginning of a file and re-read it because reading the file from disk is so "expensive". A seek() will flush all buffers and you are basically starting from scratch again (Data has to be re-read from disk). For a small file, read it into memory - do necessary edits and then write it back out. "Small" is a relative term, but your file certainly qualifies!

    Another Update: The general approach of your code is seriously flawed in terms of extensibility (make "Friday" the second line if its not already the second line). What happens if the input file has "Tuesday" as the second line? Then Friday needs to go on the 3rd line! The above code would do that.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11125925]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (1)
As of 2024-04-25 01:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found