Не могу прочитать общедоступный скаляр внутри подпрограммы
Код perl выглядит следующим образом: проблема в том, что я не могу прочитать $key внутри sub tweak_server {}....
my $key;
my %hash = ( flintstones => [ "C:/Users1/f1.xml", "C:/Users1/f2.xml" ],
jetsons => [ "C:/Users2/f1.xml" ],
simpsons => [ "C:/Users3/f1.xml", "C:/Users3/f1.xml", "C:/Users3/f1.xml" ], );
foreach $key (keys%hash){
if (scalar@{$hash{$key}}>1){
foreach my $path (@{$hash{$key}}){
my $filehandle;
open($filehandle, "+<$path") or die "cannot open out file out_file:$!";
my $roots = { TAG => 1 };
my $handlers = { 'ROOT/TAG' => \&tweak_server,
};
my $twig = new XML::Twig(TwigRoots => $roots,
TwigHandlers => $handlers,
twig_print_outside_roots => \*$filehandle);
$twig->parsefile($path);
say $key;#could read key
sub tweak_server {
my ($twig, $root) = @_;
my $tag2=$root->first_child_text('TAG2');
say $key;# could not read
if ($tag2=~/$key/){
#BLABLA
}
$twig->flush( $filehandle, pretty_print => 'indented');
}
}
}
}
как я утверждаю, ключ $ может быть прочитан вне подпрограммы, но не внутри. Появилась ошибка: использование неинициализированного значения $key
а потом я попробовал простую ситуацию, так же, как
my $a="aaa";
open( $filehandle, "+<$path") or die "cannot open out file out_file:$!";
my $roots = { TAG => 1 };
my $handlers = { 'ROOT/TAG' => \&tweak_server,
};
my $twig = new XML::Twig(TwigRoots => $roots,
TwigHandlers => $handlers,
twig_print_outside_roots => \*$filehandle
);
$twig->parsefile($path);
sub tweak_server {
say $a;
my ($twig, $root) = @_;
my $tags=$root->first_child_text('TAG2');
my $str="204B";
if ($tag2=~m/$str/){
foreach my $b(1...6){
say $a; }
}
$twig->flush( $filehandle, pretty_print => 'indented');
}
в этом коде можно прочитать $a.... я потратил один день на это, но все еще не могу это исправить... сумасшедший теперь спасибо заранее!!
2 ответа
Вы декларируете $key
за пределами for
петля. Затем внутри цикла вы определяете подпрограмму, которая закрывается на $key
,
Как правило, объявляйте переменные в наименьшей применимой области видимости. Например:
for my $key (keys ...) {
или же
open my $filehandle, '<', ...
Почему вы определяете sub tweak_server
в теле for
цикл? Мне кажется, что вы хотите сделать, это определить новую анонимную подпрограмму для каждой итерации.
Во-первых, короткий пример, который повторяет то, что вы наблюдаете:
use warnings; use strict;
my $key;
my %hash = qw(a b c d e f);
foreach $key (keys %hash) {
somesub();
sub somesub {
print "$key\n";
}
}
Теперь исправим:
use warnings; use strict;
my %hash = qw(a b c d e f);
foreach my $key (keys %hash) {
my $somesub = sub { print "$key\n" };
$somesub->();
}
Таким образом, мы определяем новую анонимную функцию на каждой итерации, и каждая новая подпрограмма закрывается по каждому значению переменной цикла.
С точки зрения вашего кода, вы должны заменить именованный суб с
my $tweak_server = sub {
my ($twig, $root) = @_;
my $tag2=$root->first_child_text('TAG2');
say $key;# could not read
if ($tag2=~/$key/){
#BLABLA
}
$twig->flush( $filehandle, pretty_print => 'indented');
}
my $handlers = {
'ROOT/TAG' => $tweak_server,
};
Или, еще лучше, как заметил @mirod, пройти $key
в tweak_server
:
sub tweak_server {
my( $key, $twig, $root)= @_;
...
}
И в теле цикла,
my $handlers = {
'ROOT/TAG' => sub { tweak_server($key, @_) },
};
Короткая версия: Если у вас есть названное суб-объявление (sub foo { ... }
) внутри другого подпункта или внутри цикла, вы, вероятно, делаете что-то не так, вы, вероятно, хотите анонимный подпункт. Это тот случай, здесь.
sub foo { ... }
в основном так же, как
BEGIN { *foo = sub { ... }; }
Если подпункт относится к любому лексическомуmy
) переменные вне себя, они будут захвачены, когда sub
выполняется во время компиляции с именованными подпрограммами. Распространенной ошибкой является
sub outer {
my ($x) = @_;
sub inner {
... $x ...
}
outer();
}
Это не работает, потому что my $x
вызывается несколько раз для создания нескольких переменных, но inner
захватывает только тот, который существовал во время компиляции. (К счастью, это предупреждает.)
На первый взгляд, ваш код имеет только одну переменную с именем $key
, поэтому не должно быть проблем с захватом во время компиляции. Но они выглядят обманчиво. Благодаря использованию псевдонимов foreach, переменная итератора не является той переменной, которая вообще была вне цикла.
$ perl -E'
my $x; say "x: ", 0+\$x; # Show address of variable.
my $y; say "y: ", 0+\$y;
my $z; say "z: ", 0+\$z;
for $x ($y, $z) { say "i: ", 0+\$x; }
'
x: 155771632
y: 155771584
z: 155771744
i: 155771584
i: 155771744
В вашем случае вы имели
$ perl -E'
my $x; say "x: ", 0+\$x;
my $y; say "y: ", 0+\$y;
my $z; say "z: ", 0+\$z;
for $x ($y, $z) {
say "i: ", 0+\$x;
sub f { say "f: ", 0+\$x; }
f();
}
'
x: 142992144
y: 142992096
z: 142992256
i: 142992096
f: 142992144
i: 142992256
f: 142992144
Вы хотите, чтобы саб захватить $x
во время выполнения, и это делается с помощью анонимного подпрограммы.
$ perl -E'
my $x; say "x: ", 0+\$x;
my $y; say "y: ", 0+\$y;
my $z; say "z: ", 0+\$z;
for $x ($y, $z) {
say "i: ", 0+\$x;
my $f = sub { say "f: ", 0+\$x; };
$f->();
}
'
x: 159675200
y: 159675152
z: 159675312
i: 159675152
f: 159675152
i: 159675312
f: 159675312
Fix:
for my $key (keys %hash) {
if (@{ $hash{$key} } > 1) {
for my $path (@{ $hash{$key} }) {
open(my $fh, "+<", $path) # +< ???
or die("Can't open \"$path\": $!\n");
my $tweak_server = sub {
my ($twig, $root) = @_;
...
};
my $twig = new XML::Twig->new(
TwigRoots => { TAG => 1 },
TwigHandlers => { 'ROOT/TAG' => $tweak_server },
twig_print_outside_roots => $fh, # No need for \*$fh
);
$twig->parsefile($path);
}
}
}
Вы также можете использовать анонимную оболочку, которая передает переменные в качестве аргументов именованной подпрограмме.
sub tweak_server {
my ($fh, $key, $twig, $root) = @_;
...
}
for my $key (keys %hash) {
if (@{ $hash{$key} } > 1) {
for my $path (@{ $hash{$key} }) {
open(my $fh, "+<", $path) # +< ???
or die("Can't open \"$path\": $!\n");
my $twig = new XML::Twig->new(
TwigRoots => { TAG => 1 },
TwigHandlers => {
'ROOT/TAG' => sub { tweak_server($fh, $key, @_) },
},
twig_print_outside_roots => $fh, # No need for \*$fh
);
$twig->parsefile($path);
}
}
}