Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl command line search and replace with multiple expressions

Tags:

perl

I am using Perl to search and replace multiple regular expressions: When I execute the following command, I get an error:

prompt> find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g' -pe 's/(\W)##/\1/g'
syntax error at -e line 2, near "s/(\W)##/\1/g"
Execution of -e aborted due to compilation errors.
xargs: perl: exited with status 255; aborting

Having multiple -e is valid in Perl, then why is this not working? Is there a solution to this?

like image 631
blue_whale Avatar asked May 03 '12 02:05

blue_whale


2 Answers

Several -e's are allowed.

You are missing the ';'

find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g;' -pe 's/(\W)##/\1/g;'

Perl statements has to end with ;. Final statement in a block doesn't need a terminating semicolon. So a single -e without ; will work, but you will have to add ; when you have multiple -e statements.

like image 118
dpp Avatar answered Jan 03 '23 03:01

dpp


Having multiple -e values are valid, but is it useful? The values from the multiple -e are merely combined into one program, and it's up to you to ensure that together they make a syntactically correct program. The B::Deparse program can show you what perl thinks the program is:

$ perl -MO=Deparse -e 'print' -e 'q(Hello'  -e ')'
print "Hello\n";
-e syntax OK

A curious thing to note is that a newline snuck in there. Think about how it got there to see what else perl is doing to combine multiple -e values.

In your program, you are substituting on the current line, then taking the modified line and substituting again. That's better written as:

prompt> find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g; s/(\W)##/\1/g'

Now, if you are building up this command line by adding more and more -e through some automated process and you don't know ahead of time what you get, maybe those -e make sense. However, you might consider that you can do the same thing to build up the string you give to -e. I don't know what might be better because you didn't explain why you are doing it that way.

But, I suspect that in some cases, people are actually thinking about having only one substitution work. They want to try one and if its pattern doesn't work, try a different one until one succeeds. In that case you don't want to separate the substitutions by semicolons. Use the short-circuiting || instead. The s/// returns the number of substitutions it made and || will stop (short circuit) when it finds a true value:

prompt> find "*.cpp" | xargs perl -i -pe 's/##(\W)/\1/g || s/(\W)##/\1/g'

And note, you only need one -p. It only does its job once. Here's the program with multiple -p deparsed:

$ perl -MO=Deparse -i -pe 's/##(\W)/\1/g;' -pe 's/(\W)##/\1/g;'
BEGIN { $^I = ""; }
LINE: while (defined($_ = readline ARGV)) {
    s/##(\W)/$1/g;
    s/(\W)##/$1/g;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

It's the same thing as having only one -p:

$ perl -MO=Deparse -pi -e 's/##(\W)/\1/g;' -e 's/(\W)##/\1/g;'
BEGIN { $^I = ""; }
LINE: while (defined($_ = readline ARGV)) {
    s/##(\W)/$1/g;
    s/(\W)##/$1/g;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK
like image 40
brian d foy Avatar answered Jan 03 '23 04:01

brian d foy