2

I'm trying to replace a string in multiple files. But only the first file gets replaced. The code is:

perl -pi -e 's/(^<section_begin>.*\s+$)/\1<expand>\n/ if $.==1' *exp`

grep -iw "expand" *exp
a3exp:<expand>

If I use the same command for an individual file, it's working:

perl -pi -e 's/(^<section_begin>.*\s+$)/\1<expand>\n/ if $.==1' n2exp
grep -iw "expand" *exp
a3exp:<expand>
n2exp:<expand>

Can you please help me to resolve this issue?

0

5 Answers 5

4

According to perlvar:

$. is reset when the filehandle is closed, but not when an open filehandle is reopened without an intervening close().

Because "<>" never does an explicit close, line numbers increase across ARGV files (but see examples in eof).

(second emphasis added)

When you use -p, @ARGV is set to the list of files you passed on the command line. Since $. is not reset when you go to the next file, it will only equal 1 for the first file. You can see this by simply printing $.:

$ echo foo > foo
$ echo bar > bar
$ perl -pe 'print "$. "' *
1 bar
2 foo

(with -p lines are printed automatically, so in the above example, the content of each line is printed next to the value of $.)

If you close the special filehandle ARGV when you move to the next file as mpapec suggests, $. will behave as you expect:

$ perl -pe 'print "$. "; close ARGV if eof' *
1 bar
1 foo

See the documentation for eof for more details and a couple of good examples.

Sign up to request clarification or add additional context in comments.

Comments

3

By closing ARGV filehandle, line counter $. gets reset.

http://perldoc.perl.org/perlvar.html#ARGV

The special filehandle that iterates over command-line filenames in @ARGV

So you need to explicitly close file when eof() is reached,

perl -pi -e'
  s/(^<section_begin>.*\s+$)/\1<expand>\n/ if $.==1;
  close ARGV if eof;
' *exp

Comments

2

The Unix shell has a lot of tools to workaround this problem. There's find with -exec:

find . -name \*exp -exec perl -pi -e '... if $.==1' {} \;

or an old-fashioned for loop. In bash that would look like

for file in *exp
do
    perl -pi -e '... if $.==1' file
done

3 Comments

Sorry, was misusing xargs
Your find -exec approach will invoke the Perl interpreter once for each file. It would be better to pipe the results of find to Perl, something like: find -print0 | perl -p0i -e ... (assuming your version of find has -print0)
But of course that approach doesn't fix the OP's problem, so never mind :)
2

You can use eof or eof(ARG) to detect the end of each file and reset $. accordinly.

From the Documentation

In a while (<>) loop, eof or eof(ARGV) can be used to detect the end of each file, whereas eof() will detect the end of the very last file only. Examples:

# reset line numbering on each input file
while (<>) {
    next if /^\s*#/;  # skip comments
    print "$.\t$_";
} continue {
    close ARGV if eof;  # Not eof()!
}

http://perldoc.perl.org/functions/eof.html

Comments

0

I think your if $.==1 is getting you. That's lines processed: you're telling it to just process the first line of the input.

Comments

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.