in reply to Inserting a line in the middle of a text

Here's an in-place edit example:

my $file = "mydata.txt"; # read data from file open my $fh, '+<', $file or die "can't open file $file : $!\n"; my @data = <$fh>; chomp @data; # modify data (add item) @data = sort (@data, 'Simon'); # rewind back to beginning seek($fh, 0, 0); for my $d (@data) { # write data back to file (skips blank lines) print $fh "$d\n" if $d; } # if new file is shorter than original, throw away extra truncate($fh, tell($fh)); close $fh;

Note that opening the file as '+<' means 'update mode'. The basic idea is read the file, modify the contents, then rewind back to the beginning with seek, write the new data back to the file - overwriting the original contents. If the new file is larger, you could stop here, but if it's shorter, you'll need to throw away the "extra" using truncate (so it's best to always use it, just in case). You might want to apply file locking here, depending on the application. For example, if you were using this in a webapp to add new users to an email list, you could potentially have multiple processes trying to update the file at the same time. Just add a flock($fh,2) after the open statement.

Also note that this assumes that the file is not so big that it can't be read into memory all at once.

You might also want to read up on perl's -i command line switch (in place edit). It actually copies the input to a temp file, reads from the temp file, and writes to the original file name - so the end results is that the output overwrites the original input.

Replies are listed 'Best First'.
Re^2: Inserting a line in the middle of a text
by davido (Cardinal) on Jan 20, 2009 at 17:15 UTC

    That seems like a contortion to avoid opening an input filehandle and a separate output filehandle.

    use strict; use warnings; my $filename = "inputfile.txt"; my $outputfile = $filename . "out"; my( $infh, $outfh ); open( $infh, '<', $filename ) or die $!; open( $outfh, '>', $outputfile ) or die $!; while( my $line = <$infh> ) { if( $line =~ /\bWilliam\b/i ) { print $outfh "Nick\n"; } print $line; } close $infh or die $!; close $outfh or die $!; rename $outputfile, $filename or die $!;

    This is pretty close to what the -i commandline switch does for Perl one liners, with the exception of the "-i.bak" behavior, which could easily be implemented here too.

    This whole thing could be written as a one liner, by the way:

    perl -pi.bak -e "/\bWilliam\b/i && print qq/Nick\n/;" filename.txt

    Obviously I forgot the names you're searching on, but the principle works.


    Dave

      There are some advantages to working in-place over creating a temporary file:

      • File permission and ownership is maintained.
      • Using a temporary file requires more permissions.
      • Using a temporary file may complicate the locking system required.
      • Working in-place can be faster in some situations.

      This is pretty close to what the -i commandline switch does for Perl one liners

      -i is closer to

      open(my $fh_in, '<', $qfn); unlink($qfn); open(my $fh_out, '>', $qfn); ...

      for now.