awk是否支持动态的用户定义变量?

fan*_*lix 5 bash awk dynamic

awk支持这个:

awk '{print $(NF-1);}'
Run Code Online (Sandbox Code Playgroud)

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

awk '{a=123; b="a"; print $($b);}'
Run Code Online (Sandbox Code Playgroud)

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

a=123;
b="a";
eval echo \${$b};
Run Code Online (Sandbox Code Playgroud)

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

Gre*_*Fox 6

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

允许使用不支持动态变量,函数名称的语言的想法非常简单.在程序中的某个状态下,您需要动态的任何内容来自行修改代码,并从中断处继续执行.a eval(),就是这样.

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

为了实现这一切,您有三个主要问题

  1. 如何获得自我,以便我们可以修改它
  2. 如何加载修改后的代码,并从我们中断的地方继续
  3. 寻找解释器接受我们修改过的代码的方法

如何获得自我,以便我们可以修改它

这是一个适合直接执行的示例代码.这是我为运行gawk的环境注入的基础设施,因为它需要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";
}'
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,只要操作系统不能使用我们的args并且/proc/可用,就可以阅读我们自己.这可能一开始看起来没用,但是我们需要它来支持我们的堆栈的推/弹,这样我们的执行状态就可以嵌入代码中了,所以我们可以保存/恢复并在操作系统关闭/重启时继续存在

我遗漏了操作系统检测功能和bootloader(用awk编写),因为如果我发布它,孩子们可以构建独立于平台的多线程代码,并且很容易对它造成严重破坏.

如何加载修改后的代码,并从我们中断的地方继续

现在,通常你有push()pop()登记,所以你可以保存自己的状态和自己玩,并从你离开的地方继续.a调用和读取堆栈是获取内存地址的典型方法.

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

这个想法很简单.代码应该是,而不是让awk控制你的循环,而在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"
}'
Run Code Online (Sandbox Code Playgroud)

结果:

与我们的原始代码完全相同,除了

_(BEGIN_MODIFY"|");print "65964";_("|"ND_MODIFY)
Run Code Online (Sandbox Code Playgroud)

function bar_56228_(s){print "doe"}
Run Code Online (Sandbox Code Playgroud)

在代码的最后

现在,这似乎毫无用处,因为我们只print "foo";用我们的pid替换代码.但是当有多个_()具有单独的MAGIC字符串来识别BLOCKS时,它变得有用,并且客户制作了多行字符串替换例程而不是sub()

你应该为堆栈,函数列表,执行点提供BLOCKS作为最低限度.

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

function bar_56228_(s){print "doe"}
function bar_88128_(s){print "doe"}
...
Run Code Online (Sandbox Code Playgroud)

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

不要忘记提供一个客户自己的remove_block()函数,以便代码保持合理的大小,而不是每次执行时都增长.

寻找解释器接受我们修改过的代码的方法

通常调用二进制文件是微不足道的.然而,当在awk中这样做时,变得困难.你可能会说system()就是这样.

有两个问题.

  1. system()可能不适用于某些环境
  2. 当你执行代码时它会阻塞,trus你无法执行重复呼叫并让用户同时感到高兴.

如果必须使用system(),请确保它不会阻止.正常通话system("sleep 20 && echo from-sh & ")无效.解决方案很简单,

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

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

  • 太好了,你提供的比我想要的多。就像病毒感染外壳一样。 (2认同)

Gre*_*Fox 4

现在不行。然而,如果你提供一个包装器,它是可能的(有点老套和肮脏)。这个想法是使用 @ 运算符,这是在最新版本的 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"
}
Run Code Online (Sandbox Code Playgroud)

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

function get(varname, this, call){call="get_"varname;return @call();}
function set(varname, arg, this, call){call="set_"varname; @call(arg);}
Run Code Online (Sandbox Code Playgroud)

所以现在,对于您想要通过名称访问的每个变量名称,您声明这两个函数

function get_my_var(){return my_var;}
function set_my_var(arg){my_var = arg;}
Run Code Online (Sandbox Code Playgroud)

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

BEGIN{ my_var = ""; }
Run Code Online (Sandbox Code Playgroud)

声明它供全球访问。然后你可以使用

get("my_var");
set("my_var", "whatever");
Run Code Online (Sandbox Code Playgroud)

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

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

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