I have an array of hashes in Perl. I would like to loop through the array and execute some code specified in a string on each of the hashes (remark: I would not like to discuss this approach or its security considerations; let's focus on how it can be done). An example implementation could look like this:
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $item->{foo}};
foreach my $item (@$list) {
eval qq{$code};
}
As expected, this prints:
foo1
foo2
foo3
foo4
So far, so good. However, I would like the array and hash data structure to be transparent to the code snippet, i.e. the code snippet should be able to use $foo
instead of $item->{foo}
etc. Therefore, I am trying to import the hash entries into the main
namespace. (Again, I would like to discuss how to do this, not whether I should do it.)
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
$main::{$key} = \$item->{$key};
}
{
no strict "vars";
eval qq{$code};
}
}
I would expect this to work, but it does not (fully). This code prints:
Use of uninitialized value $foo in say at (eval 1) line 1.
foo2
foo3
foo4
For some reason, the first item is not processed correctly.
I have tried several ways to circumvent this or find out more about what is wrong. The following changes all work (potentially with a warning about $foo
only being used once):
eval qq{$code}
with a literal say $foo
my $code = q{say $foo}
with my $code = q{say ${$main::{foo}}}
(however, $main::foo
does not suffice)$foo
somewhere before, e.g. put a { no strict "vars"; my $bar = $foo; }
before the outer foreach loopeval sprintf('our $%s;', $key);
before $main::{$key} = \$item->{$key};
Option 4 is what I would do now. It feels a bit hacky, but does the job. However, I would still like to understand why this is necessary and whether there is a better way to fix it.
UPDATE: As pointed out by TLP, a simpler way to achieve this is to use a symbolic reference instead of symbol table manipulation. See my own answer to the question. However, I would still like to understand why the symbol table approach does not work out of the box.
A symbolic reference is a simpler way than manipulation of the symbol table. Here is an adaptation of the code in the question that solves the problem:
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
no strict 'refs';
${$key} = $item->{$key};
}
{
no strict 'vars';
eval qq{$code};
}
}
Importantly, the solution above makes a copy of the values in the data structure instead of creating an alias. This matters if the code in $code
modifies the values. The modified code example below showcases this. It also contains (commented out) the fixed symbol table manipulation, which creates an alias instead, thereby allowing the original data structure to be modified by $code
. See also the answer by clamp.
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{$foo .= '_bar'; say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
no strict 'refs';
${$key} = $item->{$key}; # copy
# *{$key} = \$item->{$key}; # alias
}
{
no strict 'vars';
eval qq{$code};
}
say $item->{foo};
}