【问题标题】:Capture variable assignments in a Perl eval在 Perl eval 中捕获变量赋值
【发布时间】:2011-11-23 23:28:20
【问题描述】:

我希望能够从 Perl eval 中捕获变量赋值。也就是说,确定在代码中分配了哪些变量名并提取它们的值。

例如,如果我运行:

eval '$foo=42; $bar=3.14;'

eval 的结果是 3.14(最后评估的值),但我也希望能够确定名称“$foo”和“$bar”及其值(事先不知道名称)。

我已经阅读了通过 Safe 和 Eval::Context 将变量插入到 eval 块中的几种方法,但还没有任何提取它们的方法。我更熟悉 Python 的 eval/exec,它内置了对此的支持。

【问题讨论】:

    标签: perl


    【解决方案1】:

    在 eval 中声明的任何词法变量都将在 eval 结束后丢失。要捕获和隔离在 eval 中设置的全局变量,您可以考虑使用 Safe 模块来创建新的全局命名空间。类似于以下内容:

    use Safe;
    
    my $vars = Safe->new->reval(qq{
        $code_to_eval; 
        $code_to_search_the_symbol_table_for_declared_variables
    });
    

    搜索代码定义为遍历嵌套的%main:: 符号表以搜索任何感兴趣的变量。你可以让它返回一个包含信息的数据结构,然后你可以用它做你喜欢的事情。

    如果您只担心在根级别定义的变量,您可以编写如下内容:

    use strict;
    use warnings;
    
    my $eval_code = '$foo=42; $bar=3.14;';
    
    use Safe;
    my $vars = Safe->new->reval(
        $eval_code . q{;
        my %vars;
        for my $name (keys %main::) {
            next if $name =~ /::$/    # exclude packages
             or not $name =~ /[a-z]/; # and names without lc letters
    
            my $glob = $main::{$name};
            for (qw($SCALAR @ARRAY %HASH)) {
                my ($sigil, $type) = /(.)(.+)/;
                if (my $ref = *$glob{$type}) {
                    $vars{$sigil.$name} = /\$/ ? $$ref : $ref
                }
            }
        }
        \%vars
    });
    
    print "$_: $$vars{$_}\n" for keys %$vars;
    # $foo: 42
    # $bar: 3.14
    

    搜索代码还可以使用 Padwalker 使用 peek_my 函数在当前词法范围内搜索任何已定义变量。

    【讨论】:

    • 什么版本的 Perl 开始允许从字符串访问 glob 槽?我本可以发誓我曾经在尝试做类似的事情时遇到“不是哈希”错误。所以,我想我一定是在某个 delta 页面上错过了这一点。
    【解决方案2】:

    根据 Eric Strom 的建议,这是我尝试充实基于 Safe 的解决方案。

    package main;
    use warnings; use strict;
    use Safe;
    
    my $cpt = new Safe;
    
    $cpt->permit_only(qw(sassign lineseq padany const rv2sv leaveeval));
    my $name_space = $cpt->root;
    
    my $no_strict = 0;
    #
    # populate the clean symbol table
    #
    $cpt->reval('0');
    die "safe compartment initialisation error: $@" if $@;
    my %symtab_clean = do {no strict 'refs'; %{$name_space.'::'} }; 
    
    my $result = $cpt->reval('$foo=42; $bar=3.14;', $no_strict);
    
    if ($@) {
        warn "eval error: $@";
    }
    else {
        #
        # symbol table additions
        #
        my %symtab_dirty = do {no strict 'refs'; %{$name_space.'::'} }; 
    
        my @updated_variables = grep { ! exists $symtab_clean{$_} } (sort keys %symtab_dirty);
    
        foreach my $variable (@updated_variables) {
            my $value = do{ no strict 'refs'; ${$name_space.'::'.$variable} };
           print "variable $variable was set to: $value\n"
        }
    }
    

    注意事项:

    1. 上面允许使用最小限制的 saf 操作码集。见perl opcodes
    2. 我选择在执行命令之前和之后寻找差异

    【讨论】:

      【解决方案3】:

      好吧,eval 运行后 $foo 的值将是 42。使 $foo 不可见的范围没有变化。

      但这当然是假设预先知道 $foo 的存在。

      无论如何,这只能在没有use strict 设置的情况下工作,这几乎可以肯定是个坏主意。使用use strict,脚本将无法编译。我假设您的示例更复杂,并且您正在寻找由eval 执行的现有哈希的更改。但目前尚不清楚您的eval 是如何不知道它发生了什么变化的,除非发生某种动态代码注入。

      【讨论】:

        猜你喜欢
        • 2011-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-11
        • 2012-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多