'How can I overwrite file after replace the word?

I want to replace the text in the file and overwrite file.

use strict;
use warnings;

my ($str1, $str2, $i, $all_line);
$str1 = "AAA";
$str2 = "bbb";

open(RP, "+>", "testfile") ;

$all_line = $_;
$i = 0;
while(<RP>) {
        while(/$str1/) {
        $i++;
        }
        s/$str1/$str2/g;
        print RP $_;
}
close(RP);


Solution 1:[1]

A normal process is to read the file line by line and write out each line, changed as/if needed, to a new file. Once that's all done rename that new file, atomically as much as possible, so to overwrite the orgiinal.

Here is an example of a library that does all that for us, Path::Tiny

use warnings;
use strict;
use feature 'say';

use Path::Tiny;
    
my $file = shift || die "Usage: $0 file\n";

say "File to process:";
say path($file)->slurp;

# NOTE: This CHANGES THE INPUT FILE
#
# Process each line: upper-case a letter after .
path($file)->edit_lines( sub { s/\.\s+\K([a-z])/\U$1/g } );

say "File now:";
say path($file)->slurp;

This upper-cases a letter following a dot (period), after some spaces, on each line where it is found and copies all other lines unchanged. (It's just an example, not a proper linguistic fix.)

Note: the input file is edited in-place so it will have been changed after this is done.


This capability was introduced in the module's version 0.077, of 2016-02-10. (For reference, Perl version 5.24 came in 2016-08. So with the system Perl 5.24 or newer, a Path::Tiny installed from an OS package or as a default CPAN version should have this method.)

Solution 2:[2]

Perl has a built-in in-place editing facility: The -i command line switch. You can access the functionality of this switch via $^I.

use strict;
use warnings;

my $str1 = "AAA";
my $str2 = "bbb";

local $^I = '';  # Same as `perl -i`. For `-i~`, assign `"~"`.
local @ARGV = "testfile";

while (<>) {
   s/\Q$str1/$str2/g;
   print;
}

Solution 3:[3]

I was not going to leave an answer, but I discovered when leaving a comment that I did have some feedback for you, so here goes.

open(RP, "+>", "testfile") ;

The mode "+>" will truncate your file, delete all it's content. This is described in the documentation for open:

You can put a + in front of the > or < to indicate that you want both read and write access to the file; thus +< is almost always preferred for read/write updates--the +> mode would clobber the file first. You can't usually use either read-write mode for updating textfiles, since they have variable-length records. See the -i switch in perlrun for a better approach.

So naturally, you can't read from the file if you first delete it's content. They mention here the -i switch, which is described like this in perl -h:

-i[extension]     edit <> files in place (makes backup if extension supplied)

This is what ikegami's answer describes, only in his case it is done from within a program file rather than on the command line.

But, using the + mode for both reading and writing is not really a good way to do it. Basically it becomes difficult to print where you want to print. The recommended way is to instead read from one file, and then print to another. After the editing is done, you can rename and delete files as required. And this is exactly what the -i switch does for you. It is a predefined functionality of Perl. Read more about it in perldoc perlrun.

Next, you should use a lexical file handle. E.g. my $fh, instead of a global. And you should also check the return value from the open, to make sure there was not an error. Which gives us:

open my $fh, "<", "testfile" or die "Cannot open 'testfile': $!";

Usually if open fails, you want the program to die, and report the reason it failed. The error is in the $! variable.

Another thing to note is that you should not declare all your variables at the top of the file. It is good that you use use strict; use warnings, Perl code should never be written without them, but this is not how you handle it. You declare your variable as close to the place you use the variable as possible, and in the smallest scope possible. With a my declared variable, that is the nearest enclosing block { ... }. This will make it easy to trace your variable in bigger programs, and it will encapsulate your code and protect your variable.

In your case, you would simply put the my before all the variables, like so:

my $str1 = "AAA";
my $str2 = "bbb";
my $all_line = $_;
my $i = 0;

Note that $_ will be empty/undefined there, so that assignment is kind of pointless. If your intent was to use $all_line as the loop variable, you would do:

while (my $all_line = <$fh>) {

Note that this variable is declared in the smallest scope possible, and we are using a lexical file handle $fh.

Another important note is that your replacement strings can contain regex meta characters. Sometimes you want them to be included, like for example:

my $str1 = "a+";   # match one or more 'a'

Sometimes you don't want that:

my $str1 = "google.com";  # '.' is meant as a period, not the "match anything" character

I will assume that you most often do not want that, in which case you should use the escape sequence \Q ... \E which disables regex meta characters inside it.


So what do we get if we put all this together? Well, you might get something like this:

use strict;
use warnings;

my $str1 = "AAA";
my $str2 = "bbb";
my $filename = shift || "testfile";   #  'testfile', or whatever the program argument is

open my $fh_in,  "<", $filename        or die "Cannot open '$filename': $!";
open my $fh_out, ">", "$filename.out"  or die "Cannot open '$filename.out': $!";

while (<$fh_in>) {           # read from input file...
    s/\Q$str1\E/$str2/g;     # perform substitution...
    print $fh_out $_;        # print to output file
}

close $fh_in;
close $fh_out;

After this finishes, you may choose to rename the files and delete one or the other. This is basically the same procedure as using the -i switch, except here we do it explicitly.

rename $filename, "$filename.bak";  # save backup of input file in .bak extension
rename "$filename.out", $filename;  # clobber the input file

Renaming files is sometimes also facilitated by the File::Copy module, which is a core module.

With all this said, you can replace all your code with this:

perl -i -pe's/AAA/bbb/g' testfile

And this is the power of the -i switch.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3