【问题标题】:Does awk support dynamic user-defined variables?awk 是否支持动态用户定义变量?
【发布时间】:2021-08-10 21:33:23
【问题描述】:

awk 支持这个:

awk '{print $(NF-1);}'

但不适用于用户定义的变量:

awk '{a=123; b="a"; print $($b);}'

顺便说一句,shell 支持这个:

a=123;
b="a";
eval echo \${$b};

如何在 awk 中实现我的目的?

【问题讨论】:

  • awk 有数组,甚至是二维数组(gawk 4.0 支持真正的二维数组)为什么不考虑使用数组来满足您的要求呢?或者只是举个例子,你想处理/获取什么输入/输出。
  • 对于数组[a][b][c],我使用的是数组[a,b,c]。但是 array1=array[c], array[a,b]=array1 更好
  • 在 bash 中,你应该使用${!b} 来做变量间接,而不是eval
  • shell != bash${!b} 在 bash 中有效,但在“shell”中无效,最好将其解释为通用 bourne shell。

标签: bash dynamic awk


【解决方案1】:

好的,因为我们中的一些人喜欢用鼻子吃意大利面,这是我过去写的一些实际代码:-)
首先,使用不支持它的语言获得可自我修改的代码将是非常重要的。

在一种不支持动态变量、函数名称的语言中允许使用动态变量、函数名称的想法非常简单。在程序的某个状态下,您需要一个动态的任何东西来自行修改您的代码并恢复执行 从你离开的地方。 eval(),也就是。

如果语言支持eval() 和这样的等价物,这一切都非常简单。但是,awk 没有这样的功能。因此,你,程序员必须提供这样的接口。

要让这一切发生,你有三个主要问题

  1. 如何获取我们的 self 以便我们修改它
  2. 如何加载修改后的代码,并从中断处继续
  3. 找到一种方法让解释器接受我们修改后的代码

如何获取我们的 self 以便我们修改它

这是一个示例代码,适合直接执行。 这是我为运行 gawk 的环境注入的 infastrucure,因为它需要 PROCINFO

echo ""| awk '
function push(d){stack[stack[0]+=1]=d;}
function pop(){if(stack[0])return stack[stack[0]--];return "";}
function dbg_printarray(ary , x , s,e, this , i ){
 x=(x=="")?"A":x;for(i=((s)?s:1);i<=((e)?e:ary[0]);i++){print x"["i"]=["ary[i]"]"}}
function dbg_argv(A ,this,p){
 A[0]=0;p="/proc/"PROCINFO["pid"]"/cmdline";push(RS);RS=sprintf("%c",0);
 while((getline v <p)>0)A[A[0]+=1]=v;RS=pop();close(p);}
{
    print "foo";
    dbg_argv(A);
    dbg_printarray(A);
    print "bar";
}'

结果:

foo
A[1]=[awk]
A[2]=[
function push(d){stack[stack[0]+=1]=d;}
function pop(){if(stack[0])return stack[stack[0]--];return "";}
function dbg_printarray(ary , x , s,e, this , i ){
 x=(x=="")?"A":x;for(i=((s)?s:1);i<=((e)?e:ary[0]);i++){print x"["i"]=["ary[i]"]"}}
function dbg_argv(A ,this,p){
 A[0]=0;p="/proc/"PROCINFO["pid"]"/cmdline";push(RS);RS=sprintf("%c",0);
 while((getline v <p)>0)A[A[0]+=1]=v;RS=pop();close(p);}
{
print "foo";
dbg_argv(A);
dbg_printarray(A);
print "bar";
}]
bar

如您所见,只要操作系统不玩我们的args,并且/proc/可用,就可以 阅读我们的自我。起初这可能看起来没用,但我们需要它来推送/弹出我们的堆栈, 这样我们的执行状态就可以嵌入到代码中,这样我们就可以保存/恢复并在操作系统关闭/重启时幸存下来

我省略了操作系统检测功能和引导加载程序(用 awk 编写),因为如果我发布它, 孩子们可以构建独立于平台的多正规代码,而且很容易造成破坏。

如何加载修改后的代码,并从中断处继续

现在,通常你有 push()pop() 用于寄存器,所以你可以保存你的状态并玩 你的自我,并从你离开的地方继续。调用并读取您的堆栈是获取 内存地址。

不幸的是,在 awk 中,在正常情况下我们不能使用指针(没有很多脏活), 或寄存器(除非您可以在此过程中注入其他东西)。 但是,您需要一种方法来暂停和恢复您的代码。

这个想法很简单。而不是让 awk 控制你的循环和 while,if else 条件, 递归深度和你所在的功能,代码应该。 保留堆栈、变量名列表、函数名列表,并自行管理。 只要确保你的代码总是不断调用self_modify( bool ),这样即使突然失败, 一旦脚本重新运行,我们就可以输入self_modify( bool ) 并恢复我们的状态。 当您想自行修改代码时,您必须提供定制的 write_stack()read_stack() 代码,将堆栈状态写为字符串,并从 从代码嵌入字符串本身中取出值,并恢复执行状态。

这是演示整个流程的一小段代码

echo ""| awk '
function push(d){stack[stack[0]+=1]=d;}
function pop(){if(stack[0])return stack[stack[0]--];return "";}
function dbg_printarray(ary , x , s,e, this , i ){
 x=(x=="")?"A":x;for(i=((s)?s:1);i<=((e)?e:ary[0]);i++){print x"["i"]=["ary[i]"]"}}
function _(s){return s}
function dbg_argv(A ,this,p){
 A[0]=0;p="/proc/"PROCINFO["pid"]"/cmdline";push(RS);RS=sprintf("%c",0);
 while((getline v <p)>0)A[A[0]+=1]=v;RS=pop();close(p);}
{
    _(BEGIN_MODIFY"|");print "#foo";_("|"END_MODIFY)
    dbg_argv(A);
    sub( \
    "BEGIN_MODIFY\x22\x5c\x7c[^\x5c\x7c]*\x5c\x7c\x22""END_MODIFY", \
    "BEGIN_MODIFY\x22\x7c\x22);print \"#"PROCINFO["pid"]"\";_(\x22\x7c\x22""END_MODIFY" \
     ,A[2]) 
    print "echo \x22\x22\x7c awk \x27"A[2]"";
    print "function bar_"PROCINFO["pid"]"_(s){print \x22""doe\x22}";
    print "\x27"
}'

结果:

和我们原来的代码完全一样,除了

_(BEGIN_MODIFY"|");print "65964";_("|"ND_MODIFY)

function bar_56228_(s){print "doe"}

代码末尾

现在,这似乎没用,因为我们只是将代码 print "foo"; 替换为我们的 pid。 但它变得有用,当有多个 _() 带有单独的 MAGIC 字符串来识别 BLOCKS 时, 和一个定制的多行字符串替换例程,而不是sub()

您必须至少为堆栈、函数列表、执行点提供 BLOCKS。

注意最后一行包含bar 这本身只是一个刺痛,但是当这段代码重复执行时,请注意

function bar_56228_(s){print "doe"}
function bar_88128_(s){print "doe"}
...

而且它还在不断增长。虽然这个例子是故意制作的,所以它没有任何用处, 如果我们提供一个例程来调用bar_pid_(s) 而不是那个print "foo" 代码, 突然这意味着我们手上有eval() :-) 现在,是不是 eval() 有用:-)

不要忘记提供定制的 remove_block() 函数,以便代码维护 合理的大小,而不是每次执行时都增长。

找到一种方法让解释器接受我们修改后的代码

通常调用二进制文件是微不足道的。但是,当在 awk 中这样做时,它变得很困难。 你可能会说 system() 是一种方式。

这有两个问题。

  1. system() 可能不适用于某些环境
  2. 它会在您执行代码时阻塞,因为您无法执行递归调用并同时让用户满意。

如果您必须使用system(),请确保它不会阻塞。 对system("sleep 20 &amp;&amp; echo from-sh &amp; ") 的正常呼叫将不起作用。 解决方法很简单,

echo ""|awk '{print "foo";E="echo ep ; sleep 20 && echo foo & disown ; ";  E | getline v;close(E);print "bar";}'

现在你有一个不阻塞的异步 system() 调用:-)

【讨论】:

  • 非常棒,你提供的比我想要的多。就像病毒感染外壳一样。
【解决方案2】:

暂时没有。但是,如果你提供一个包装器,它是(有点老套和肮脏的)可能的。 这个想法是使用在最新版本的 gawk 中引入的 @ 运算符。

这个@运算符通常用于按名称调用函数。 所以如果你有

function foo(s){print "Called foo "s}
function bar(s){print "Called bar "s}
{
    var = "";
    if(today_i_feel_like_calling_foo){
        var = "foo";
    }else{
        var = "bar";
    }
    @var( "arg" ); # This calls function foo(), or function bar() with "arg"
}

现在,这本身就很有用。 假设我们事先知道 var 名称,我们可以编写一个包装器来间接修改和获取 vars

function get(varname, this, call){call="get_"varname;return @call();}
function set(varname, arg, this, call){call="set_"varname; @call(arg);}

所以现在,对于您希望通过名称进行访问的每个 var 名称,您声明这两个函数

function get_my_var(){return my_var;}
function set_my_var(arg){my_var = arg;}

也许,在你的 BEGIN{} 块中的某个地方,

BEGIN{ my_var = ""; }

声明它以供全局访问。 然后就可以使用了

get("my_var");
set("my_var", "whatever");

这可能一开始看起来没什么用,但是有非常好的用例,例如 通过将 var 的名称保存在另一个 var 的数组中来保持 var 的链接列表,等等。 它也适用于数组,老实说,我用它来嵌套和链接数组 数组,所以我可以像使用指针一样遍历多个数组。

您也可以通过这种方式编写引用 awk 中的 var 名称的配置脚本, 实际上也有一个解释器内部的解释器类型的东西......

不是最好的做事方式,但是,它可以完成工作,我不必担心 空指针异常,或 GC 等 :-)

【讨论】:

  • 如果你有 gawk 版本 >3.18 间接函数 @ 是支持的。 git repo 6f3612539c425da2bc1 于 2010 年 11 月 18 日,或者 cvs 从 2009 年初开始拥有它
【解决方案3】:

$ 符号不是变量的标记,如在 shell、PHP、Perl 等中。它是一个运算符,它接收一个 整数n并从输入中返回第 n 列。因此,您在第一个示例中所做的不是动态设置/获取变量,而是调用运算符/函数。

正如评论者所说,您可以使用数组归档您正在寻找的行为:

awk '{a=123; b="a"; v[b] = a; print v[b];}'

【讨论】:

  • 感谢$的描述。但你的回答没有帮助,v[b]=a 不应该出现。
  • 我的意思是:awk '{ v["a"]=123; b="a";打印 v[b];}'
  • @fanlix 为什么不呢?我的意思是,如果您解释您要做什么以及为什么要这样做,我们可以提供更好的帮助。
【解决方案4】:

我有一个类似的问题要解决,从“.ini”文件加载设置,我使用数组来动态设置变量。

它适用于 Awk 或 Gawk、Linux 或 Windows (GnuWin32)

gawk -v Settings_File="my_settings_file.ini" -f awk_script.awk <processing_file>

[my_settings_file.ini]

#comment
first_var=foo
second_var=bar

[awk_script.awk]

BEGIN{
    FS="=";
    while((getline < Settings_File)>0) {
        if($0 !~ /^[#;]|^(\s*)$/) {
            var_array[$1] = $2;
        }
    }

    print var_array["first_var"];
    print var_array["second_var"];

    if (var_array["second_var"] == "bar") {
        print "works!";
    }
}
{
#more processing
}
END {
#finish processing
}

【讨论】:

    猜你喜欢
    • 2014-02-04
    • 1970-01-01
    • 2021-12-16
    • 2018-01-21
    • 2011-01-24
    • 2016-11-12
    • 2015-12-01
    • 2014-03-25
    • 1970-01-01
    相关资源
    最近更新 更多