I want to replace a string in a file. Of course I can use
perl -pi -e 's/pattern/replacement/g' file
but I want to do it with a script.
Is there any other way to do that instead of system("perl -pi -e s/pattern/replacement/g' file")?
-i takes advantage that you can still read an unlinked filehandle, you can see the code it uses in perlrun. Do the same thing yourself.
use strict;
use warnings;
use autodie;
sub rewrite_file {
my $file = shift;
# You can still read from $in after the unlink, the underlying
# data in $file will remain until the filehandle is closed.
# The unlink ensures $in and $out will point at different data.
open my $in, "<", $file;
unlink $file;
# This creates a new file with the same name but points at
# different data.
open my $out, ">", $file;
return ($in, $out);
}
my($in, $out) = rewrite_file($in, $out);
# Read from $in, write to $out as normal.
while(my $line = <$in>) {
$line =~ s/foo/bar/g;
print $out $line;
}
rewrite { s/foo/bar/ } $file. It doesn't exist, but it's easy to write.system that you can do in Perl. It's faster (no forking and loading a new Perl process). There's no shell escapes to worry about. There's no potential security hole. There's no cross platform quoting issues. There's no problem calling the wrong thing named perl in your PATH (use $^X instead).You can duplicate what Perl does with the -i switch easily enough.
{
local ($^I, @ARGV) = ("", 'file');
while (<>) { s/foo/bar/; print; }
}
You can try the below simple method. See if it suits your requirement best.
use strict;
use warnings;
# Get file to process
my ($file, $pattern, $replacement) = @ARGV;
# Read file
open my $FH, "<", $file or die "Unable to open $file for read exited $? $!";
chomp (my @lines = <$FH>);
close $FH;
# Parse and replace text in same file
open $FH, ">", $file or die "Unable to open $file for write exited $? $!";
for (@lines){
print {$FH} $_ if (s/$pattern/$replacement/g);
}
close $FH;
1;
file.txt:
Hi Java, This is Java Programming.
Execution:
D:\swadhi\perl>perl module.pl file.txt Java Source
file.txt
Hi Source, This is Source Programming.
You can handle the use case in the question without recreating the -i flag's functionality or creating throwaway variables. Add the flag to the shebang of a Perl script and read STDIN:
#!/usr/bin/env perl -i
while (<>) {
s/pattern/replacement/g;
print;
}
Usage: save the script, make it executable (with chmod +x), and run
path/to/the/regex-script test.txt
(or regex-script test.txt if the script is saved to a directory in your $PATH.)
Going beyond the question:
If you need to run multiple sequential replacements, that's
#!/usr/bin/env perl -i
while (<>) {
s/pattern/replacement/g;
s/pattern2/replacement2/g;
print;
}
As in the question's example, the source file will not be backed up. Exactly like in an -e oneliner, you can back up to file.<backupExtension> by adding a backupExtension to the -i flag. For example,
#!/usr/bin/env perl -i.bak
You can use
sed 's/pattern/replacement/g' file > /tmp/file$$ && mv /tmp/file$$ file
Some sed versions support the -i command, so you won't need a tmpfile. The -i option will make the temp file and move for you, basicly it is the same solution.
Another solution (Solaris/AIX) can be using a here construction in combination with vi:
vi file 2>&1 >/dev/null <@
1,$ s/pattern/replacement/g
:wq
@
I do not like the vi solution. When your pattern has a / or another special character, it will be hard debugging what went wrong. When replacement is given by a shell variable, you might want to check the contents first.
but I want to do it with a script which is a bad solution. Good that the OP accepted the Perl answer! I just added the answer for someone who finds a Perl solution in a shell script and is wondering about a solution without Perl.
perl -MO=Deparse -pi -e 's/pattern/replacement/g' > my-string-replacing-script.plwill get you most of the way there