¿Cómo usar una variable en el lado de reemplazo del operador de sustitución de Perl?


Me gustaría hacer lo siguiente:

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

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

Esperaría que var var contuviera "foo middle bar", pero no funciona. Tampoco:

$replace='foo \1 bar';

De alguna manera me estoy perdiendo algo con respecto a la fuga.


Arreglé la 's' que faltaba

Author: Jonathan Leffler, 2008-12-25

8 answers

En el lado de reemplazo, debe usar $1, no \1.

Y solo puedes hacer lo que quieras haciendo reemplazar una expresión evaluable que da el resultado que quieres y diciéndole a s / / / que la evalúe con el modificador / ee de la siguiente manera:

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

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

print "var: $var\n";

Para ver por qué se necesitan "" y double /e, vea el efecto de la doble evaluación aquí:

$ perl
$foo = "middle";
$replace='"foo $foo bar"';
print eval('$replace'), "\n";
print eval(eval('$replace')), "\n";
__END__
"foo $foo bar"
foo middle bar
 69
Author: ysth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-25 10:38:46

Deparse nos dice que esto es lo que se está ejecutando:

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

Sin embargo,

 /$find/foo \1 bar/

Se interpreta como:

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

Desafortunadamente parece que no hay una manera fácil de hacer esto.

Puedes hacerlo con una evaluación de cadenas, pero eso es peligroso.

La solución más sana que funciona para mí fue esta:

$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; 

Una refutación contra la técnica ee:

Como dije en mi respuesta, evalo evaluaciones por una razón.

$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";

Este código hace exactamente lo que piensa.

Si su cadena de sustitución está en una aplicación web, acaba de abrir la puerta a la ejecución de código arbitrario.

Buen Trabajo.

Además, NO funcionará con las contaminaciones activadas por esta misma razón.

$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.

Sin embargo, la técnica más cuidadosa es sana, segura, segura, y no falla mancha. (Tenga la seguridad de que la cadena que emite todavía está contaminada, por lo que no pierde ninguna seguridad. )

 12
Author: Kent Fredric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-26 04:39:30
# perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234

Tenga cuidado, sin embargo. Esto hace que se produzcan dos capas de eval, una para cada e al final de la expresión regular:

  1. sub sub > > {1
  2. value 1 value > valor final, en el ejemplo, 1234
 6
Author: eruciform,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-17 01:17:06

Como otros han sugerido, podrías usar lo siguiente:

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';   # 'foo \1 bar' is an error.
my $var = "start middle end";
$var =~ s/$find/$replace/ee;

Lo anterior es la abreviatura de lo siguiente:

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';
my $var = "start middle end";
$var =~ s/$find/ eval($replace) /e;

Prefiero la segunda a la primera ya que no oculta el hecho de que eval(EXPR) se utiliza. Sin embargo, ambos de los errores de silencio anteriores, por lo que lo siguiente sería mejor:

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';
my $var = "start middle end";
$var =~ s/$find/ my $r = eval($replace); die $@ if $@; $r /e;

Pero como puede ver, todo lo anterior permite la ejecución de código Perl arbitrario. Lo siguiente sería mucho más seguro:

use String::Substitution qw( sub_modify );

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';
my $var = "start middle end";
sub_modify($var, $find, $replace);
 4
Author: ikegami,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-24 18:32:19

Yo sugeriría algo como:

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

Es bastante legible y parece ser seguro. Si se necesita reemplazo múltiple, es fácil:

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
 1
Author: Pavel Coodan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-20 02:16:47

Ver ESTEanterior post sobre el uso de una variable en el lado de reemplazo de s/// en Perl. Mira tanto la respuesta aceptada como la respuesta refutación .

Lo que está tratando de hacer es posible con la forma s///ee que realiza un doble eval en la cadena de la derecha. Ver operadores similares a comillas de perlop para más ejemplos.

Se advierte que hay impilcations de seguridad de eval y esto no funcionará en modo de mancha.

 1
Author: dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:58
#!/usr/bin/perl

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

print $res

Esto me dio el '1234'.

 0
Author: rmk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-17 00:01:19

No estoy seguro de lo que está tratando de lograr. Pero tal vez usted puede utilizar esto:

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

Es decir, simplemente deje el medio solo y reemplace el inicio y el final.

 -5
Author: PEZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-25 10:47:26