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

An easy prob seeking the light, or a better way of doing it. I have a text file which I open with
my $file="dogy";
open(FH, "+<$file")
flock(FH, 2) or die "can't lock $file $!";
I then read the file into an array, filter the entries and use seek FH, 0, 0; then I write it back out on the same FH, the problem is that as I print less lines out then existed in the original file, I have the old lines I do not want, I basically want to throw in an EOF after the last entry I write. Here is an example program showing the problem.
#!/bin/perl -w
use strict;


my $file="dogy";
open(FH, "+<$file");
flock(FH, 2);
my @newEntries;
while(my $line = <FH>) {
    chomp($line);

    if ($line%2==1) { next; }
    push(@newEntries,$line);
}

seek FH,0,0;
foreach my $ent (@newEntries) {
    print FH $ent . "\n";
}

close(FH)
 
##############
dogy has
1
2
3
4
5
6
7
8
9
10
============ and after running the script I get
2
4
6
8
10

7
8
9
10
I want want the 7 - 10 lines to not exist.

Replies are listed 'Best First'.
Re: seek in a text file
by ikegami (Patriarch) on Oct 06, 2008 at 00:29 UTC
    truncate + tell

    Update: Code:

    #!/bin/perl -w use strict; my $file="dogy"; open(FH, "+<$file"); flock(FH, 2); my @newEntries; while(my $line = <FH>) { chomp($line); if ($line%2==1) { next; } push(@newEntries,$line); } seek FH,0,0; foreach my $ent (@newEntries) { print FH $ent . "\n"; } truncate FH, tell FH; close(FH)

    Update: Even better?

    #!/bin/perl -w use strict; my $file="dogy"; open(FH, "+<$file"); flock(FH, 2); my @newEntries; while(my $line = <FH>) { chomp($line); if ($line%2==1) { next; } push(@newEntries,$line); } seek FH,0,0; truncate FH, 0; foreach my $ent (@newEntries) { print FH $ent . "\n"; } close(FH)

      Or even better?

      #!/usr/bin/perl use warnings; use strict; use Fcntl qw/ :seek :flock /; my $file = 'dogy'; open my $FH, '+<', $file or die "Cannot open '$file' $!"; flock $FH, LOCK_EX or die "Cannot flock '$file' $!"; my @newEntries = grep !($_ % 2), <$FH>; seek $FH, 0, SEEK_SET or die "Cannot seek '$file' $!"; truncate $FH, 0 or die "Cannot truncate '$file' $!"; print $FH @newEntries; close $FH;
      works good, thanks.
Re: seek in a text file
by apl (Monsignor) on Oct 06, 2008 at 11:27 UTC
    Now that you've got a solution to your problem, let me suggest that you not do this. Instead of overwriting the file, write the filtered contents to a new file name. Or rename the original file before writing the filtered contents to the specified filename.

    This provides an audit trail for what you've done, and guarantees you can fall back to the original contents should the need arise.

Re: seek in a text file
by lostjimmy (Chaplain) on Oct 06, 2008 at 01:41 UTC
    You could also open the file for reading, read it into an array, close it, filter the values, then open the file for writing (which will create a new file so those old lines won't be hanging around) and write to it.
    open my $fh, "data" or die "could not open data for reading: $!"; my @data = <$fh>; close $fh; open $fh, ">data" or die "could not open data for writing: $!"; for (0..4) { print $fh $data[$_]; } close $fh;
      That would lose the lock, introducing a race condition.
Re: seek in a text file
by talexb (Chancellor) on Oct 06, 2008 at 17:55 UTC

    I've recently used Tie::File to good effect, however I'm not sure if that module will also lock the file as you require. But it's a more Perl-ish way of accessing a file than using tell and seek.

    Alex / talexb / Toronto

    "Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds