0

I have several commands printing text to a file using perl. During these print commands I have an if statement which should delete the last 5 lines of the file I am currently writing to if the statement is true. The number of lines to delete will always be 5.

if ($exists == 0) {
  print(OUTPUT ???) # this should remove the last 5 lines
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
charles hendry
  • 1,710
  • 4
  • 13
  • 18
  • check out this question: http://stackoverflow.com/questions/345513/how-can-i-delete-the-last-n-lines-of-a-file – asf107 Feb 17 '12 at 14:24
  • 1
    From the Stack Overflow and official Perl FAQ: [How do I change, delete, or insert a line in a file, or append to the beginning of a file in Perl?](http://stackoverflow.com/questions/2322140/how-do-i-change-delete-or-insert-a-line-in-a-file-or-append-to-the-beginning) – daxim Feb 17 '12 at 14:26

6 Answers6

4

You can use Tie::File:

use Tie::File;
tie my @array, 'Tie::File', filename or die $!;

if ($exists == 0) {
    $#array -= 5;
}

You can use the same array when printing, but use push instead:

push @array, "line of text";
TLP
  • 66,756
  • 10
  • 92
  • 149
3
$ tac file | perl -ne 'print unless 1 .. 5' | tac > file.tailchopped
tchrist
  • 78,834
  • 30
  • 123
  • 180
  • 1
    @TLP: `head: unknown option -- -` and `usage: head [-count | -n count] [file ...]`. That’s not POSIX compatible. – tchrist Feb 17 '12 at 20:57
  • 1
    @TLP So what? That’s not POSIX. It’s not standard. It’s a prosaic local variant. I have at least 3 systems where it fails miserably. You should not expect other people to run non-standard software. People who live in the Linux glasshouse don’t know what the real world outside is like. – tchrist Feb 18 '12 at 01:36
  • 2
    I'd take `head -n -5` over `tac` in a portability contest any day. – socket puppet Feb 18 '12 at 03:06
  • 2
    @socketpocket Just alias *tac* to `perl -e 'print reverse <>'` — I always do. :) Honestly, I probably had that for decades before *tac* (1) started shipping. Heck, I had a *tac* before Perl, for goodness’ sake! – tchrist Feb 18 '12 at 03:50
  • @tchrist I think you're being somewhat negative here. The point is that it is better to see if `head` can be used on your system, than it is to reverse the file, print, and reverse it again. – TLP Feb 18 '12 at 12:03
  • 1
    @TLP: if you are going to suggest standard Unix tools, provide a version that doesn't use long options (as socket puppet has done). Long options are a GNU extension, and not portable. – ninjalj Feb 19 '12 at 15:53
1

Only obvious ways I can think of:

  1. Lock file, scan backwards to find a position and use truncate.
  2. Don't print to the file directly, go through a buffer that's at least 5 lines long, and trim the buffer.
  3. Print a marker that means "ignore the last five lines". Process all your files before reading them with a buffer as in #2

All are pretty fiddly, but that's the nature of flat files I'm afraid.

HTH

Richard Huxton
  • 21,516
  • 3
  • 39
  • 51
  • Not that these are "bad" suggestions, but Tie::File has the magic built-in. – Joel Berger Feb 17 '12 at 14:34
  • @Joel Berger, Not it doesn't. Tie::File reads the file from the start, so it would definitely be much slower than (1) for large files. Not to mention all the memory it would use up from creating an index of every line in the file. – ikegami Feb 17 '12 at 21:46
  • @ikegami, you are right about #1 being faster (for large files). All I had actually meant was that Tie::File would do most of the work, not that it would be more efficient – Joel Berger Feb 17 '12 at 22:54
1

As an alternative, print the whole file except last 5 lines:

open($fh, "<", $filename) or die "can't open $filename for reading: $!";
open($fh_new, ">", "$filename.new") or die "can't open $filename.new: $!";
my $index = 0; # So we can loop over the buffer
my @buffer;
my $counter = 0;
while (<$fh>) {
    if ($counter++ >= 5) {
        print $fh_new $buffer[$index];
    }
    $buffer[$index++] = $_;
    $index = 0 if 5 == $index;
}
close $fh;
close $fh_new;
use File::Copy;
move("$filename.new", $filename) or die "Can not copy $filename.new to $filename: $!";
DVK
  • 126,886
  • 32
  • 213
  • 327
  • This is #2 from Richard Huxton's answer – DVK Feb 17 '12 at 14:53
  • `$index = 0 if 5 == $index` can also be written `$index %= 5`, assuming increments of one. – TLP Feb 17 '12 at 15:22
  • 1
    Those are very strange open calls. `open` returns an undefined true value on success and zero on failure - neither of which are useful as file handles. `$file` is where the filehandle should be, but it is undeclared and you use the same variable twice. Also `$filename.new` requires double-quotes around it or it won't compile. – Borodin Feb 17 '12 at 15:25
  • @Borodin - brain hiccups - didn't have caffeine yet. You're absolutely right. – DVK Feb 17 '12 at 16:21
1

File::ReadBackwards+truncate is the fastest for large files, and probably as fast as anything else for short files.

use File::ReadBackwards qw( );

my $bfh = File::ReadBackwards->new($qfn)
   or die("Can't read \"$qfn\": $!\n");

$bfh->readline() or last for 1..5;

my $fh = $bfh->get_handle();
truncate($qfn, tell($fh))
   or die $!;

Tie::File is the slowest, and uses a large amount of memory. Avoid that solution.

ikegami
  • 367,544
  • 15
  • 269
  • 518
0

you can try something like this:

open FILE, "<", 'filename';
if ($exists == 0){
 @lines = <FILE>;
 $newLastLine = $#lines - 5;   
 @print = @lines[0 .. $newLastLine];
 print "@print";
}

or even shortened:

open FILE, "<", 'filename';
@lines = <FILE>;
if ($exists == 0){
 print "@lines[0 .. $#lines-5]";
}
ashraf
  • 537
  • 7
  • 16