web-dev-qa-db-fra.com

Comment utiliser une variable dans le côté remplacement de l'opérateur de substitution Perl?

Je voudrais faire ce qui suit:

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

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

Je m'attendrais à ce que $ var contienne "foo middle bar", mais cela ne fonctionne pas. Ni:

$replace='foo \1 bar';

D'une manière ou d'une autre, il me manque quelque chose concernant l'évasion.


J'ai corrigé le 's' manquant

42
Manu

Du côté du remplacement, vous devez utiliser $ 1, pas\1.

Et vous ne pouvez faire que ce que vous voulez en faisant remplacer une expression évaluable qui donne le résultat souhaité et en demandant à s /// de l’évaluer avec le modificateur/ee comme suit:

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

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

print "var: $var\n";

Pour voir pourquoi "" et double/e sont nécessaires, voyez l'effet de la double évaluation ici:

$ Perl
$foo = "middle";
$replace='"foo $foo bar"';
print eval('$replace'), "\n";
print eval(eval('$replace')), "\n";
__END__
"foo $foo bar"
foo middle bar
73
ysth

Deparse nous dit que c'est ce qui est en train d'être exécuté: 

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

Cependant, 

 /$find/foo \1 bar/

Est interprété comme:

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

Malheureusement, il semble qu'il n'y ait pas de moyen facile de le faire. 

Vous pouvez le faire avec une chaîne eval, mais c'est dangereux. 

La solution la plus saine qui a fonctionné pour moi était la suivante: 

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

Une réfutation contre la technique ee:

Comme je l'ai dit dans ma réponse, j'évite les évaluations pour une raison. 

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

ce code fait exactement ce que vous pensez qu'il fait. 

Si votre chaîne de substitution se trouve dans une application Web, vous venez d'ouvrir la porte à l'exécution de code arbitraire. 

Bon travail. 

En outre, il WON'T / travailler avec les souillures allumé pour cette raison même.

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

Cependant, la technique la plus prudente est la suivante: saine et sauve, et ne manque pas de teinture (Soyez assuré que la chaîne qu'il émet est toujours souillée, vous ne perdez donc aucune sécurité.)

12
Kent Fredric
# Perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234

Soyez prudent, cependant. Cela provoque deux couches de eval, une pour chaque e à la fin de la regex:

  1. $ sous -> 1 $
  2. 1 $ -> valeur finale, dans l'exemple, 1234
6
eruciform

Comme d'autres l'ont suggéré, vous pouvez utiliser les éléments suivants:

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;

Ce qui précède est l'abréviation de:

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

Je préfère le second au premier car il ne cache pas le fait que eval(EXPR) est utilisé. Cependant, les deux erreurs de silence ci-dessus, il serait donc préférable de:

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;

Mais comme vous pouvez le constater, tout ce qui précède permet l'exécution de code Perl arbitraire. Ce qui suit serait beaucoup plus sûr:

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
ikegami

Voir THIS précédent SO post sur l'utilisation d'une variable du côté de remplacement de s///in Perl. Regardez à la fois la réponse acceptée et la réfutation réponse. 

Ce que vous essayez de faire est possible avec le formulaire s///ee qui exécute une double eval sur la chaîne de droite. Voir perlop cite comme des opérateurs pour plus d'exemples. 

Soyez averti qu'il existe des impilcations de sécurité de eval et que cela ne fonctionnera pas en mode altéré. 

1
dawg

Je suggérerais quelque chose comme:

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

Il est assez lisible et semble être en sécurité. Si plusieurs remplacements sont nécessaires, c'est facile:

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
1
Pavel Coodan
#!/usr/bin/Perl

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

print $res

Cela m'a valu le «1234».

0
rmk