johnkasta has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks, I am searching for this string in an entire directory:
if ((!($session_login)) or ($session_access < 5)) { header("Window-Target: _top"); header("Location: index.php"); }

and I want to replace all instances of this string with:
if ((!($session_login)) or ($session_access < 5)) { header("Window-Target: _top"); header("Location: index.php"); exit; }

I am using this regex find replace at the shell:
perl -pi -w -e 's/header\(\"Window\-Target: _top\"\);header\(\"Locatio +n: index.php\"\); /header\(\"Window\-Target: _top\"\);header\(\"Locat +ion: index.php\"\);exit;/g;' *.php

no changes are happening on my directory. Which of my regexs are wrong? I am beginning the match at the first header.
(note: i was using \s as 'whitespace' but shell didn't like it so i just put the actual whitespace in the regex)
Thanks in advance,
Perl Noob,
johnkasta

Replies are listed 'Best First'.
Re: find and replace from shell
by jwkrahn (Abbot) on Jun 16, 2010 at 19:38 UTC

    Using the -p switch means that you are only reading one line at a time.    You need to either read in the whole file or change the Input Record Separator to something like "}\n".

    This may work, but it is UNTESTED:

    perl -i -pe' BEGIN { $/ = "}\n" } s/@{[ <<REGEX ]}/@{[ <<STRING ]}/g; if ((!(\$session_login)) or (\$session_access < 5)) { header("Window-Target: _top"); header("Location: index.php"); } REGEX if ((!(\$session_login)) or (\$session_access < 5)) { header("Window-Target: _top"); header("Location: index.php"); exit; } STRING ' *.php
      jwkrahn, thanks for your input!

      1. please excuse the noobie question but when i copy and past this into my shell, iterm lays it out vertically and does not wait for my 'enter' to execute. how do i execute this in the shell?

      2. perhaps it would be easier to change the -p to something else to tell it to read the whole file. what should I replace the -p with?
      Thanks!

        1. In my shell, (xterm) when I want to insert a newline I enter the two keys <CTRL>vj.
        2. To read the whole file you need to use the -0 switch with the value -0777 along with the -p switch.
      So the the BEGIN { $/ = "}\n" } changes the input record separator?

      Should I substitute REGEX with the regex for the find, and the STRING with the replace? Not sure how much of this actually goes in the shell prompt. Did you write REGEX and STRING as placeholders? Can anyone assist?

        Not sure if you have been answered in the Chatterbox already, so...

        So the the BEGIN { $/ = "}\n" } changes the input record separator?

        Yes. See $/ in perlvar.

        Should I substitute REGEX with the regex for the find, and the STRING with the replace? Not sure how much of this actually goes in the shell prompt. Did you write REGEX and STRING as placeholders? Can anyone assist?

        I, also, cannot test jwkrahn's code of Re: find and replace from shell, but it looks like it should work as it stands. The  <<REGEX and  <<STRING begin 'here-documents': see <<EOF in perlop. What happens when you try the code on an expendable test file?

Re: find and replace from shell
by johnkasta (Initiate) on Jun 16, 2010 at 19:41 UTC
    Edit- I found out that because the shell reads each file one line at a time, it would be impossible to do a find and replace with regex on this string
    if ((!($session_login)) or ($session_access < 5)) { header("Window-Target: _top"); header("Location: index.php"); }

    Is it true that I cannot do a regex find on a 'string' that spans multiple line breaks?
    if so, does someone have a syntax example for how to make a little script that could do this find and replace?
    Thanks!
      Here's a perl script that you could run with one or more file names as command line args, and each file would be edited:
      #!/usr/bin/perl use strict; for my $file ( @ARGV ) { { local $/; open( my $fh, "<", $file ) or do { warn "unable to read $file\ +n"; next }; $_ = <$fh>; } s/( \{ \s* header\("Window-Target:\s*_top"\); \s* header\("Location:\s*index\.php"\); ) \s* \} /$1\n exit;\n}/gx; { open( my $fh, ">", $file ) or do { warn "unable to edit $file\ +n"; next }; print $fh $_; } }
      Note the following:
      • using the "x" modifier (so the regex can be arranged more legibly)
      • using capturing parens in regex to simplify replacement
      • using "\s*" to match all the (potentially variable) white space (this is the only way you must use something like this to match white space when using the "x" modifier, but you should be handling white space this way in any case)
      • using lexically-scoped file handles in minimal blocks
      • using local $/ for slurp mode (in same scope as file handle)
      • skipping files that can't be read or written (and showing a warning each time)
      • if the script is run a second time on the same file, it'll just rewrite the file without changing it (because the regex won't match)
      And if I'm taking the trouble to store this edit operation as a little perl script all by itself (which I think is a Good Idea), I would also take the time to add some POD commentary to say why I wrote this script in the first place.
        Thanks Graff!
        can I get an example on the correct syntax for inputting the command line args?

        so in my shell, would that be something like...

        mypath/findreplace.pl _test.php

        also, are there substitutions to make in the script, such as $fh (my filehandle)?
        Thank you very much.
        Hi Graff,
        I was able to get this to work. I am currently testing to see if can do the find replace on an entire directory.
        Thanks for your help Graff and JWKhaun so much. As a new perl programmer, I really appreciate this resource.