Single-liner for one-line substitute?

M

Mike Pearson

Hi -

I know almost no Perl at all, but I occasionally use

perl -pi -e 's/old/new/g' file

for a global search/replace in a file. I've tried to modify this to
change only a string on the first line of a file, leaving the string
unchanged elsewhere in the file, but I haven't been able to find a way
to do this. Simply removing the 'g' has no effect - it still does a
global replace.

Can anyone tell me how to do this?

Thanks -

Mike
 
A

anno4000

Mike Pearson said:
Hi -

I know almost no Perl at all, but I occasionally use

perl -pi -e 's/old/new/g' file

for a global search/replace in a file. I've tried to modify this to
change only a string on the first line of a file, leaving the string
unchanged elsewhere in the file, but I haven't been able to find a way
to do this. Simply removing the 'g' has no effect - it still does a
global replace.

The match operator is applied to every line in the file. The /g
modifier changes the behavior of each application. It does not
work across applications.

Here is one way:

perl -pi -e '$. == 1 && s/old/new/g' file

Anno
 
P

Paul Lalli

The match operator is applied to every line in the file. The /g
modifier changes the behavior of each application. It does not
work across applications.

Here is one way:

perl -pi -e '$. == 1 && s/old/new/g' file

That would still cause Perl to loop through the entire file, each time
checking the value of $., even though we know it will only match the
first time. I wonder if this might be "better":

perl -pi -e's/old/new/g; exit;' file

That way, regardless of the success or failure of the s///, the program
ends after the first iteration of the implicit while(<>) loop....

Paul Lalli
 
A

Andrew

Paul said:
That would still cause Perl to loop through the entire file, each time
checking the value of $., even though we know it will only match the
first time. I wonder if this might be "better":

perl -pi -e's/old/new/g; exit;' file

That way, regardless of the success or failure of the s///, the program
ends after the first iteration of the implicit while(<>) loop....

Interesting, Paul, i wasn't aware of the implicit "while".

I just tried your suggestion, however, and it mysteriously zeroed out
the file. Using "last;" in place of "exit;" yields the same empty-file
result.

I intuit you're on the right track (if, indeed, a "while (<>)" is
implied), but perhaps there needs to be an additional explicit command
preceding "exit" or "last", which forces the modified data to be
written back to the file?

andrew
 
P

Paul Lalli

Andrew said:
Interesting, Paul, i wasn't aware of the implicit "while".

Take a look at perldoc perlrun, for the -p and -n options.
I just tried your suggestion, however, and it mysteriously zeroed out
the file. Using "last;" in place of "exit;" yields the same empty-file
result.

WHOOPS! You are absolutely right. I was completely forgetting how
the -p and -i options work, in that they print each line to the
newly-modified file right after that line has been read (and possibly
modified by the -e'' code). Definately cannot put an exit or last
there.

Profuse apologies to the OP and to Anno, for my erroneous "correction".

Paul Lalli
 
A

Andrew

Mike said:
Many thanks - that's done the job.

Two variations on the above -- one equivalent, the other for a
different need:
(not sure about the syntax esthetics nor efficiency of these; just
throwing them on the table):

This seems equivalent to the above (modifies ONLY the first line):

perl -pi -e 'next if $done; s/old/new/g; $done++;' file

This one modifies the first match, regardless on which line, and does
nothing beyond that first line:

perl -pi -e 'next if $done; s/old/new/g && $done++;' file

(The latter also lets you re-run the command and alter the subsequent
matching line (provided your particular regex does not match the same
line altered in the previous match) -- a sort of "incremental" way of
doing the standard "perl -pi -e" thing... can't think of a practical
use, but, heck...)

Andrew
 
A

Andrew

Bad choice of words on my part. Correcting, just to be sure:
This one modifies the first match, regardless on which line, and does
nothing beyond that first line:

I should have said, "this one modifies the first line that matches, but
not any subsequent lines that may also match"

perl -pi -e 'next if $done; s/old/new/g && $done++;' file
 
J

John W. Krahn

The match operator is applied to every line in the file. The /g
modifier changes the behavior of each application. It does not
work across applications.

Here is one way:

perl -pi -e '$. == 1 && s/old/new/g' file

Also for that to work on multiple files you need to close the filehandle:

perl -pi -e'close ARGV if eof; $. == 1 && s/old/new/g' file*



John
 
A

Andrew

John said:
Also for that to work on multiple files you need to close the filehandle:

perl -pi -e'close ARGV if eof; $. == 1 && s/old/new/g' file*

Well, heck -- same goes for my version; thanks for catching this:

perl -pi -e '$done=0 if eof; next if $done; s/old/new/g &&
$done++;' file

Also, an afterthought I had earlier: One can, of course, generalize
things further, to alter not just the first matching line, but, say,
the first 3 matching lines (and nothing else):

perl -pi -e '$count=0 if eof; next if ($count>2); s/old/new/g &&
$count++;' file1 file2 ...

(or, obviously, one can shift the subset down with something like "...
next unless ( (($count>4) && ($count<10))" , and so on, and so forth,
with any numeric comparison ("unless ($count==15)", to change only the
16th matching line) )

And, of course, reverting back to the original OP's task of replacing a
particular line, regardless of whether it matches, one can similarly
replace or try to replace any specific line or lines other than the
first one. (Separate "s/old/new/g && $count++;" into two independent
commands, in the above);

andrew
 
X

Xicheng Jia

Mike said:
Hi -

I know almost no Perl at all, but I occasionally use

perl -pi -e 's/old/new/g' file

for a global search/replace in a file. I've tried to modify this to
change only a string on the first line of a file, leaving the string
unchanged elsewhere in the file, but I haven't been able to find a way
to do this. Simply removing the 'g' has no effect - it still does a
global replace.

Can anyone tell me how to do this?

For multiple files, replace only in the first line:

perl -0777pe 's/^(.*?)old/$1new/' file*

Xicheng
 
J

John W. Krahn

Xicheng said:
For multiple files, replace only in the first line:

perl -0777pe 's/^(.*?)old/$1new/' file*

But that only substitutes one 'old' for one 'new' while the original does it
for all 'old's in the line.


John
 
X

Xicheng Jia

John said:
But that only substitutes one 'old' for one 'new' while the original does it
for all 'old's in the line.

then:

perl -i -0777pe 's/(?:\G|^)(.*?)old/$1new/g' file*

Xicheng :)
 
X

Xicheng Jia

Xicheng said:
John W. Krahn wrote:

perl -i -0777pe 's/(?:\G|^)(.*?)old/$1new/g' file*

Just make a note: the start of line anchor ^ is redundant in this
pattern when the anchor \G shows up. so it should be:

perl -i -0777pe 's/\G(.*?)old/$1new/g' file*

Xicheng
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,022
Latest member
MaybelleMa

Latest Threads

Top