我可以使用-v选项将数组传递给awk吗?

7

我希望能够将一个数组变量传递给 awk。我指的不是 shell 数组,而是本地的 awk 数组。我知道我可以像这样传递标量变量:

awk -vfoo="1" 'NR==foo' file

我可以使用相同的机制定义一个awk数组吗?类似这样:

$  awk -v"foo[0]=1" 'NR==foo' file
awk: fatal: `foo[0]' is not a legal variable name

我尝试了上述几种变体,但它们都无法在我的Debian上的GNU awk 4.1.1上运行。那么,是否有任何版本的awk(gawk、mawk或其他)可以从-v开关接受数组呢?
我知道我可以绕过这个问题,并且可以轻松想到解决方法,只是想知道是否有任何awk实现原生支持这种功能。

我并不确定答案,但我怀疑不行,因为据我所知,awk 甚至不支持像 a = [1, 2, 3] 这样的数组初始化... - Tom Fenech
1
我认为这是不可能的。请注意@TomFenech,这个初始化是不可能的,因为在awk中它们只能是关联型的。 - fedorqui
你不能在awk之外定义它,但是你可以通过序列化将bash数组转换为awk数组,参见http://stackoverflow.com/a/32887826/1435869。 - karakfa
@karakfa 谢谢,但我已经知道了。这就是为什么我特别指出“我不是指一个 shell 数组,而是一个本地的 awk 数组”。 - terdon
5个回答

7
你可以在mawk或gawk内部使用split()函数来分割“-v”值的输入(这里是gawk手册):
``` split(s, a [, r [, seps] ]) ```
将字符串s按正则表达式r拆分为数组a和分隔符数组seps,并返回字段数。*
以下是一个示例,我将"ARRAYVAR"作为值传递给awk程序中的"-v"参数,然后使用split()函数将其拆分为内部变量数组"arrayval",并打印数组的第三个值:
echo 0 | gawk -v ARRAYVAR="a,b,c,d,e,f" '{ split(ARRAYVAR,arrayval,","); print(arrayval[3]) }'
c

看起来可以工作 :)

1
是的,谢谢,我知道。这就是为什么我特别指出“我不是指shell数组,而是原生的awk数组。”问题是关于使用-v开关传递一个awk数组,而不是关于在脚本内生成数组。 - terdon
1
请注意,您可以通过输入awk 'BEGIN {actions here}来测试您的awk。无需使用echo ... | awk - fedorqui

2

如果您不坚持使用 -v,您可以使用 -i(包含)来读取包含变量设置的 awk 文件。

示例:

if F=$(mktemp inputXXXXXX); then
    cat >$F << 'END'
BEGIN {
    foo[0]=1
}
END
cat $F
    awk -i $F 'BEGIN { print foo[0] }' </dev/null
    rm $F
fi

示例跟踪(使用gawk-4.2.1):

bash -x /tmp/test.sh 
++ mktemp inputXXXXXX
+ F=inputrpMsan
+ cat
+ cat inputrpMsan
BEGIN {
    foo[0]=1
}
+ awk -i inputrpMsan 'BEGIN { print foo[0] }'
1
+ rm inputrpMsan

谢谢,但正如问题中所解释的,“我不是指一个shell数组,而是一个本地awk数组。” 这个问题是关于使用-v开关传递awk数组,而不是关于在脚本内生成数组或其他解决方法。无论如何,如果我要在文件中拥有这个数组,我只需在文件中编写整个awk脚本即可。 - terdon
在示例中,您在哪里看到了一个shell数组?在您的问题中,您指定了awk命令行上的值;对于此示例,您必须在调用使用这些值的awk之前创建一个包含这些值(来自awk)的文件。使用原始问题中的代码很难在答案中获得更好的代码。 - U. Windl
说得好,我在之前的评论中表达得不好,抱歉。我的意思是这个问题特别关注如何使用 -v 开关传递本地 awk 数组,我并不想寻找变通方法,我只是想知道是否可以使用 -v 开关实现。话虽如此,我现在已经喝了第二杯咖啡,所以我现在能够看到你的方法的好处。不,这不是我要求的,但它至少让我有一种定义数组然后在任意 awk 命令中重复使用的方法。谢谢!我猜这是 GNU awk 的一个特性,对吗? - terdon

1
看起来按定义是不可能的。
man awk中我们可以了解到:
-v var=val --assign var=val 在程序开始执行之前,将值val分配给变量var。这样的变量值可用于AWK程序的BEGIN规则。
然后我们在Using Variables in a Program中读到:
变量的名称必须是字母、数字或下划线的序列,并且不能以数字开头。 awk中的变量可以分配为数字或字符串值。
因此,由于任何使用字符=或[作为-v变量传递的一部分都不允许,因此-v实现的方式使得提供数组作为变量成为不可能。而两者都是必需的,因为awk中的数组只能是关联的。

0

很遗憾,这是不可能的。但是,您可以使用一些巧妙的方法将bash数组转换为awk数组。

最近我想通过将bash数组传递给awk来用于过滤来实现这一点,所以我做了以下操作:

$ arr=( hello world this is bash array )
$ echo -e 'this\nmight\nnot\nshow\nup' | awk 'BEGIN {
  for (i = 1; i < ARGC; i++) {
      my_filter[ARGV[i]]=1
      ARGV[i]="" # unset ARGV[i] otherwise awk might try to read it as a file
  }
} !my_filter[$0]' "${arr[@]}"

输出:

might
not
show
up

是的,谢谢,我知道。这就是为什么我特别说“我不是指一个shell数组,而是一个本地awk数组。”问题是关于使用-v开关传递awk数组,而不是在脚本内生成数组。 - terdon

-1

对于关联数组,您可以将其作为键值对字符串传递,然后在 BEGIN 部分重新格式化它。

$ echo | awk -v m="a,b;c,d" '
BEGIN {
  split(m,M,";")
  for (i in M) {
    split(M[i],MM,",")
    MA[MM[1]]=MM[2]
  }
}
{
  for (a in MA) {
    printf("MA[%s]=%s\n",a, MA[a])
  }
}'

输出:

MA[a]=b
MA[c]=d

谢谢,但正如我在问题中所说的,我知道如何为此做变通处理。问题是在特定地问是否可以将数组作为变量传递。Awk是一种完整的编程语言,因此您当然可以像您在这里描述的那样做事情。 - terdon
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接