How to modify content of a file using single file handle

436 views Asked by At

I'm trying to modify content of a file using Perl.

The following script works fine.

#!/usr/bin/perl

use strict;
use warnings;

open(FH,"test.txt") || die "not able to open test.txt $!";
open(FH2,">","test_new.txt")|| die "not able to opne test_new.txt $!";

while(my $line = <FH>)
{
        $line =~ s/perl/python/i;
        print FH2 $line;
}
close(FH);
close(FH2);

The content of test.txt:

im learning perl
im in File handlers chapter

The output in test_new.txt:

im learning python
im in File handlers chapter

If I try to use same file handle for modifying the content of file, then I'm not getting expected output. The following is the script that attempts to do this:

#!/usr/bin/perl

use strict;
use warnings;

open(FH,"+<","test.txt") || die "not able to open test.txt $!";

while(my $line = <FH>)
{
        $line =~ s/perl/python/i;
        print FH $line;
}
close(FH);

Incorrect output in test.txt:

im learning perl
im learning python
 chapter
 chapter

How do I modify the file contents using single file handle?

2

There are 2 answers

1
Yongbin Yu On BEST ANSWER

As @Сухой27 answered

it's typical situation that perl onliner pleasingly used.

perl -i -pe 's/perl/python/i'

perl takes below options

  • -p : make line by line loop(every line assign into $_ and print after evaluated $_)
  • -e : evaluate code block in above loop ( regex take $_ as default operand )
  • -i : in plcae file edit (if you pass arguments for -i, perl preserve original files with that extention)

if you run below script

perl -i.bak -pe 's/perl/python/i' test.txt

you will get modified test.txt

im learning python
im in File handlers chapter

and get original text files named in test.txt.bak

im learning perl
im in File handlers chapter
0
ikegami On

You can't delete from a file (except at the end).
You can't insert characters into a file (except at the end).

You can replace a character in a file.
You can append to a file.
You can shorten a file.
That's it.

You're imagining you can simply replace "Perl" with "Python" in the file. Those aren't of the same length, so it would require inserting characters into the file, and you can't do that.

You can effectively insert characters into a file by loading the rest of the file into memory and writing it back out two characters further. But doing this gets tricky for very large files. It's also very slow since you end up copying a (possibly very large) portion of the file every time you want to insert characters.

The other problem with in-place modifications is that you can't recover from an error. If something happens, you'll be left with an incomplete or corrupted file.

If the file is small and you're ok with losing the data if something goes wrong, the simplest approach is to load the entire file into memory.

 open(my $fh, '<+', $qfn)
    or die("Can't open \"$qfn\": $!\n");

 my $file = do { local $/; <$fh> };

 $file =~ s/Perl/Python/g;

 seek($fh, 0, SEEK_SET)
    or die $!;
 print($fh $file)
    or die $!;
 truncate($fh)
    or die $!;

A safer approach is to write the data to a new file, then rename the file when you're done.

 my $new_qfn = $qfn . ".tmp";
 open(my $fh_in, '<', $qfn)
    or die("Can't open  \"$qfn\": $!\n");
 open(my $fh_out, '<', $new_qfn)
    or die("Can't create \"$new_qfn\": $!\n");

 while (<$fh_in>) {
     s/Perl/Python/g;
     print($fh_out $_);
 }

 close($fh_in);
 close($fh_out);

 rename($qfn_new, $qfn)
    or die $!;

The downside of this approach is it might change the file's permissions, and hardlinks will point to the old content instead of the new file. You also need permissions to create a file.