1
votes

In a perl program I have, I call out to make in-place edits to a file with restricted privileges:

for my $file (@files) {
    `sudo perl -pi -e 's/foo(.?foo2)/bar\$1/m' '$file'`;
}

There may be a line break between foo and foo2, but the files use CRLF as their line break. How can I match CRLF in a -pi command with s///m or s///s?

2

2 Answers

2
votes

How about \s*? Or if you feel like being specific [\r\n]{0,2}. The former matches 0 or more whitespace, the latter 0 to 2 line feed or carriage return. You also need to not read the file in line-by-line mode, using -0777.

Perhaps a better solution than to run a number of system commands as one-liners, is to create a script file that takes a list of files, such as

system(qw(sudo perl -0777 -pi script.pl), @files);

And the script file contains only your substitution:

s/foo(?=\s*foo2)/bar/m;

I also replaced the backreference $1 with a look-ahead assertion. Note that I recommend that you run this without the -i switch or with the -i.bak backup option enabled until you know it works as intended with your files.

0
votes

Before I answer your question, let's address two things.

First of all, you have a useless use of backticks. Use system instead.

for my $file (@files) {
    system("sudo perl ...");
}

Second of all, you are not properly converting the contents of $file into a shell literal. Consider what would happen if $file contains a single-quote. Fix:

use String::ShellQuote qw( shell_quote );
for my $file (@files) {
    system(shell_quote('sudo', 'perl', ..., $file));
}

or you can avoid the shell entirely:

for my $file (@files) {
    system('sudo', 'perl', ..., $file);
}

On to your question.

-p causes Perl to wrap the program with

while (<>) {
   ... -e ...
} continue {
   print;
}

As such,

/foo(\r\nfoo2)/

or the more flexible

/foo(\s*\nfoo2)/

can't possibly match since you haven't read foo2 yet. You could cause <> to read the entire file by executing local $/; first, which can be done by adding -0777 to get

$/ = undef;
while (<>) {
   ...[ code from -e ]...
} continue {
   print;
}

So what you need is:

for my $file (@files) {
    system('sudo', 'perl', '-i', '-0777pe', 's/foo(\s*\nfoo2)/bar$1/', $file);
}

Or you could even avoid the for loop and use

system('sudo', 'perl', '-i', '-0777pe', 's/foo(\s*\nfoo2)/bar$1/', @files);