2

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")?

1
  • 2
    perl -MO=Deparse -pi -e 's/pattern/replacement/g' > my-string-replacing-script.pl will get you most of the way there Commented Oct 20, 2015 at 19:26

5 Answers 5

6

-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;
}
Sign up to request clarification or add additional context in comments.

10 Comments

Thank you, I meant an easy way of doing this.
That's not easy enough?! Put it in a function.
I could guess it on my own, just wanted to check if there is any easier way.
I get it, you want something like rewrite { s/foo/bar/ } $file. It doesn't exist, but it's easy to write.
For all the same reasons you want to avoid doing things in 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).
|
2

You can duplicate what Perl does with the -i switch easily enough.

{
    local ($^I, @ARGV) = ("", 'file');
    while (<>) { s/foo/bar/; print; }
}

3 Comments

I saw almost the same code when used this "perl -MO=Deparse -pi -e 's/pattern/replacement/g' > my-string-replacing-script.pl", but I don't understand how this code works, I don't know advanced Perl topics.
@Curiosity, Schwern tells you how it works, and you ask for something simpler. Sean gives you something simpler, and say you want to know how it works. What do you want?
But 'explain this' might be a completely separate question.
1

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.

1 Comment

Yes, this is almost what I want, Thank you!
1

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

Comments

0

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.

2 Comments

What is the advantage of this over the perl in the OP?
@Sobrique: No advantage in mixing languages, I would prefer Perl. The OP said 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.