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
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.)
| [reply] [d/l] [select] |
Re: While loop not printing anything after using grep
by LanX (Saint) on Dec 29, 2020 at 11:33 UTC
|
| [reply] [d/l] |
|
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.
| [reply] [d/l] [select] |
|
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.
| [reply] [d/l] [select] |
|
| [reply] |
Re: While loop not printing anything after using grep
by shmem (Chancellor) on Dec 30, 2020 at 00:24 UTC
|
#!/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'
| [reply] [d/l] [select] |
|
#!/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
| [reply] [d/l] [select] |
|
| [reply] [d/l] [select] |
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. | [reply] [d/l] [select] |
|
|