views:

2274

answers:

7

I would like to do the following:

$find="start (.*) end";
$replace="foo \1 bar";

$var = "start middle end";
$var =~ s/$find/$replace/;

I would expect $var to contain "foo middle bar", but it does not work. Neither does:

$replace='foo \1 bar';

Somehow I am missing something regarding the escaping.


I fixed the missing 's'

+4  A: 

Deparse tells us this is what is being executed:

$find = 'start (.*) end';
$replace = "foo \cA bar";
$var = 'start middle end';
$var =~ s/$find/$replace/;

However,

 /$find/foo \1 bar/

Is interpreted as :

$var =~ s/$find/foo $1 bar/;

Unfortunately it appears there is no easy way to do this.

You can do it with a string eval, but thats dangerous.

The most sane solution that works for me was this:

$find = "start (.*) end"; 
$replace = 'foo \1 bar';

$var = "start middle end"; 

sub repl { 
    my $find = shift; 
    my $replace = shift; 
    my $var = shift;

    # Capture first 
    my @items = ( $var =~ $find ); 
    $var =~ s/$find/$replace/; 
    for( reverse 0 .. $#items ){ 
        my $n = $_ + 1; 
        #  Many More Rules can go here, ie: \g matchers  and \{ } 
        $var =~ s/\\$n/${items[$_]}/g ;
        $var =~ s/\$$n/${items[$_]}/g ;
    }
    return $var; 
}

print repl $find, $replace, $var;

A rebuttal against the ee technique:

As I said in my answer, I avoid evals for a reason.

$find="start (.*) end";
$replace='do{ print "I am a dirty little hacker" while 1; "foo $1 bar" }';

$var = "start middle end";
$var =~ s/$find/$replace/ee;

print "var: $var\n";

this code does exactly what you think it does.

If your substitution string is in a web application, you just opened the door to arbitrary code execution.

Good Job.

Also, it WON'T work with taints turned on for this very reason.

$find="start (.*) end";
$replace='"' . $ARGV[0] . '"';

$var = "start middle end";
$var =~ s/$find/$replace/ee;

print "var: $var\n"


$ perl /tmp/re.pl  'foo $1 bar'
var: foo middle bar
$ perl -T /tmp/re.pl 'foo $1 bar' 
Insecure dependency in eval while running with -T switch at /tmp/re.pl line 10.

However, the more careful technique is sane, safe, secure, and doesn't fail taint. ( Be assured tho, the string it emits is still tainted, so you don't lose any security. )

Kent Fredric
The easy way is ysth's answer. :)
brian d foy
It depends on from where the data that's evaluated comes. Avoiding eval is generally a good idea.
PEZ
No, avoiding eval is not generally a good idea. Using it only with care is.
ysth
Telling new users to use eval, however, is not advisable.
Kent Fredric
+16  A: 

On the replacement side, you must use $1, not \1.

And you can only do what you want by making replace an evalable expression that gives the result you want and telling s/// to eval it with the /ee modifier like so:

$find="start (.*) end";
$replace='"foo $1 bar"';

$var = "start middle end";
$var =~ s/$find/$replace/ee;

print "var: $var\n";

To see why the "" and double /e are needed, see the effect of the double eval here:

$ perl
$foo = "middle";
$replace='"foo $foo bar"';
print eval('$replace'), "\n";
print eval(eval('$replace')), "\n";
__END__
"foo $foo bar"
foo middle bar
ysth
Nice example of double evaluation!
PolyThinker
That's a very nice explanation of the double evaluation :)
brian d foy
Do of course note that eval is really dangerous for web apps, especially given arbitrary strings that can't be filtered. Please see my comments for why I saw the eval way to do it and then decided not to tell the user about it!.
Kent Fredric
@Kent Fredric: Yes, absolutely there is danger if $foo or $replace come from user input, but that didn't seem likely to me from the question. And (as I see you point out) taint mode will prevent an unvetted $replace from being used.
ysth
A: 

I'm not certain on what it is you're trying to achieve. But maybe you can use this:

$var =~ s/^start/foo/;
$var =~ s/end$/bar/;

I.e. just leave the middle alone and replace the start and end.

PEZ
Time to earn your Peer Pressure badge :) Happy Christmas.
Jonathan Leffler
More badges to the people!
PEZ
Yeah, the user looks to be wanting to perform arbitrary regex in userspace and pass the whole regex to Perl.
Kent Fredric
A: 

I would suggest something like:

$text =~ m{(.*)$find(.*)};
$text = $1 . $replace . $2;

It is quite readable and seems to be safe. If multiple replace is needed, it is easy:

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
Pavel Coodan
This seems to be very slow and resource consuming, expecially if your text is very long.
Manu
A: 
#!/usr/bin/perl

$sub = "\\1";
$str = "hi1234";
$res = $str;
$match = "hi(.*)";
$res =~ s/$match/$1/g;

print $res

This got me the '1234'.

rmk
the whole point is though that I want $match and $sub to be arbitrary strings so that $sub can contain \1 with the same meaning
ldog
Can you explain your question a bit more? It's not clear what you want to achieve here...
rmk
+7  A: 
# perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234

Be careful, though. This causes two layers of eval to occur, one for each e at the end of the regex:

  1. $sub --> $1
  2. $1 --> final value, in the example, 1234
eruciform
+1: Excellent example.
drewk
As in your example, note that the assignment of `$sub='$1'` must be exactly that. `$sub='\1'` is interpreted as a reference, and `$sub="$1"` attempts performs variable interpolation. The OP is probably better severed by some form of template library at the end of the day IMHO, but still interesting example. Thanks.
drewk
+2  A: 

See THIS previous SO post on using a variable on the replacement side of s///in Perl. Look both at the accepted answer and the rebuttal answer.

What you are trying to do is possible with the s///ee form that performs a double eval on the right hand string. See perlop quote like operators for more examples.

Be warned that there are security impilcations of evaland this will not work in taint mode.

drewk
+1: cool, i didn't see the dup. you're right, this should be closed and collated...
eruciform