blue_whale
blue_whale

Reputation: 355

Perl command line search and replace with multiple expressions

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?

Upvotes: 21

Views: 10518

Answers (3)

brian d foy
brian d foy

Reputation: 132730

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

Upvotes: 2

jorgbrown
jorgbrown

Reputation: 2051

Thanks so much! You helped me reduce my ascii / decimal / 8-bit binary table printer enough to fit in a tweet:

for i in {32..126};do printf "'\x$(printf %x $i)'(%3i) = " $i; printf '%03o\n' $i | perl \
-pe 's#0#000#g;' -pe 's#1#001#g;' -pe 's#2#010#g;' -pe 's#3#011#g;' \
-pe 's#4#100#g;' -pe 's#5#101#g;' -pe 's#6#110#g;' -pe 's#7#111#g' ; done | \
perl -pe 's#= 0#= #'

Upvotes: -1

dpp
dpp

Reputation: 1758

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.

Upvotes: 59

Related Questions