Search code examples
perlcygwin

substitute a string with another in-place


In this example I would like to modify the file in-place (without pie):

# Initialise file
open my $fh, '>', 'test';
print $fh 'I love apples';
close $fh;

# Do the replacement
open $fh, '+<', 'test';
s/love/hate/ while(<$fh>);
close $fh;

# Test content (should be 'I hate apples')
open $fh, '<', 'test';
print while(<$fh>);
close $fh;

Unfortunately this example does not work. I only find this ugly solution:

# Do the replacement
open $fh, '<', 'test';
my $out;
my $changes;
while(<$fh>) {
    $changes += $_ =~ s/love/hate/;
    $out.=$_;
}
if($changes) {
    open $fh, '>', 'test';
    print $fh $out;
}
close $fh;

Any better solution?

Requirement: I only want to touch my file if there is something to change. Hint: I'm on Windows/Cygwin :(


Solution

  • Unless the substitution has the same length; you have to rewrite the file from the substitution point forward to insert/erase even a letter in it.

    -i option is usually implemented using a temporary file. All your changes are written to a temporary file that is renamed at the end to the original filename:

    $ perl -i -pe's/a/bc/g' input1
    $ <input2 perl -pe's/a/bc/g' >output && replace output input2
    $ <input3 perl -pe's/a/bc/g' | sponge input3
    

    i.e.,

    while(<$fhorig>) {
      $changed += $_ =~ s/love/haaate/;
      print $fhtmp $_;
    }
    # close, fsync files..
    rename $tmp, $orig if $changed;
    

    If input file is small; you could make changes in memory instead without the temporary file. Your code in the question does it.

    If the substitution has the same length then you could mmap the file and make the changes inplace. Both Windows and Unix support mmap. It allows to work with a large file as though it were a string or you could emulate it using read/seek/write.