3

I am trying to understand the perl one liners in cli.
E.g if I need to do a substitution in a file if I do:

perl -e 's/eat/read/g' file  

Nothing happens.

If I do:

perl -i -e 's/eat/read/g' file

Nothing happens either to the file.

Only if I do:

perl -p -i -e 's/eat/read/g' file

the substitution inline in the file happens.
If I do

perl -n -i -e 's/eat/read/g' file

the file becomes empty!

Can someone please explain the differences and what happens in each case?

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Jim
  • 18,826
  • 34
  • 135
  • 254
  • 1
    I suggest you to read this post:http://stackoverflow.com/questions/6302025/perl-flags-pe-pi-p-w-d-i-t – Casimir et Hippolyte Feb 01 '14 at 13:32
  • 2
    [perlrun](http://perldoc.perl.org/perlrun.html) tells you what each of these do. – brian d foy Feb 01 '14 at 14:01
  • The first does nothing, because none of the flags are set invoking the behavior of feeding the file to your script. The second does nothing for the same reason. The third works because you've told Perl to iterate through a file and send output to the file as an in-place edit. ...and you've told Perl that you always want to output $_ for each line. The fourth fails because, though you're iterating over the file and doing an in-place edit, you never send any output to the file, so it becomes empty. – DavidO Feb 01 '14 at 16:55

2 Answers2

8

B::Deparse module will show what switches do to your perl code,

perl -MO=Deparse -e 's/eat/read/g' file
s/eat/read/g;

nothing interesting here, your code executes as it is.

perl -MO=Deparse -i -e 's/eat/read/g' file
BEGIN { $^I = ""; }
s/eat/read/g;

ok, -i changed global variable $^I for inline editing, but no file is being read, and no loop here.

perl -MO=Deparse -p -i -e 's/eat/read/g' file
BEGIN { $^I = ""; }
LINE: while (defined($_ = <ARGV>)) {
    s/eat/read/g;
}
continue {
    print $_;
}

so -p adds while(){} loop where every line is read, altered by your code, and written again to file (print in continue{} block does that)

perl -MO=Deparse -n -i -e 's/eat/read/g' file
BEGIN { $^I = ""; }
LINE: while (defined($_ = <ARGV>)) {
    s/eat/read/g;
}

so -n doesn't have continue{} block with print like -p switch, and you have to print manually after s/// substitution if you want to make desired changes to the file (otherwise nothing is printed and your file ends truncated as you've already noticed).

mpapec
  • 50,217
  • 8
  • 67
  • 127
2

Excerpt from perl -h:

Usage: perl [switches] [--] [programfile] [arguments]
  -e program        one line of program (several -e's allowed, omit programfile)
  -i[extension]     edit <> files in place (makes backup if extension supplied)
  -n                assume "while (<>) { ... }" loop around program
  -p                assume loop like -n but print line also, like sed

When editing a file in place -i you also need to print something to the file, either with -p or explicitly (using -n) 's/eat/read/; print'

A common pattern for in place editing is

perl -pi.bak -e 's/eat/read/g' file

Which will modify your file as expected, saving a backup with suffix .bak

grebneke
  • 4,414
  • 17
  • 24
  • But when I use the snippet with `perl -p -e '...'` nothing is printed – Jim Feb 01 '14 at 13:47
  • @Jim - Given `file` with content `abc`, `perl -p -e 's/a/X/' file` prints `Xbc`. Double check your file and your commandline. – grebneke Feb 01 '14 at 13:50