在awk中,是否可能在不指定索引的情况下向数组中添加一个项?

27
我知道awk有关联数组,但我想知道是否有awk等效于这个功能:http://php.net/manual/en/function.array-push.php。显然的解决办法是只需说:
array[$new_element] = $new_element

然而,这似乎比必要的更不易读且更加 hackish。


2
我会称之为优雅和简约,而不是hackish!;-)。你总可以编写自己的函数来管理数组,但语言本身没有内置的功能。祝好运。 - shellter
在其他解决方案中建议将元素存储在 length(A)+1 会导致 gawk 报错 attempt to use scalar 'A' as an array,进而需要 更多的解决方法。因此,在我看来,你的“hackish”解决方案是最具可移植性的。 - TheDudeAbides
3个回答

21

我认为在awk中数组长度不是立即可用的(至少在我使用的版本中是这样)。但你可以简单地维护一个长度,然后像这样做:

array[arraylen++] = $0;

然后通过相同的整数值访问它的元素:

for ( i = 0; i < arraylen; i++ )
   print array[i];

3
在GAWK中,length()函数将返回数组中元素的数量,但由于数组是稀疏的,因此长度不一定是最后一个元素。 - Dennis Williamson
4
仅供历史参考,length(arrayname) 的表示方式并不是只有 GAWK 才有。它在 2002 年被添加到了 One True Awk 中。可能是三年后,这个功能才出现在 gawk 中。 - ghoti
@ghoti - Github上的链接(用于onetrueawk)承认Arnold Robbins。 - Happy Green Kid Naps
@HappyGreenKidNaps - 是的,Arnold Robbins是POSIX 1003.2投票组的成员,所以除了参与gawk的开发之外,他还帮助定义了awk的标准。如果你在以色列,一定要请他喝啤酒。 :) 另外,进一步的调查揭示了Gawk的功能历史记录,表明length()函数可以用于数组,从3.1版本开始,该版本于2001年6月发布。不确定我从哪里得到的2005年的想法,那个链接已经失效了。 - ghoti
你希望数组中的项目保持特定的顺序还是最终以特定的顺序结束?你需要保留还是消除重复项? - tomc

13
gawk中,你可以使用length(var)来获取数组的长度,因此自己编写函数并不是很难。
function push(A,B) { A[length(A)+1] = B }

注意这个讨论,不过 -- 我现在能够访问的所有地方都只有gawk 3.1.5,所以我无法对我的功能进行正确测试,呃。但是这是一个近似值。

vnix$ gawk '# BEGIN: make sure arr is an array
>   BEGIN { delete arr[0] }
>   { print "=" length(arr); arr[length(arr)+1] = $1;
>     print length(arr), arr[length(arr)] }
>   END { print "---";
>     for (i=1; i<=length(arr); ++i) print i, arr[i] }' <<HERE
> fnord foo
> ick bar
> baz quux
> HERE
=0
1 fnord
=1
2 ick
=2
3 baz
---
1 fnord
2 ick
3 baz

4
A[length(A)+1]” 这段代码片段不能保证避免冲突。它可以在某些情况下使用,例如您的示例中,只有按可预测的顺序向数组添加元素时。但是,如果您要删除数组元素,则会创建间隙,这会减少 length(),同时保留最高数字。 - ghoti
我想要做这件事,但是希望像日志轮换一样,有一个最大长度和时间限制。我想要追踪我的CPU温度,但只保留之前的十个或二十个记录。 - nyxee
@nyxee,你可以使用固定长度的数组作为循环缓冲区,通过将索引递增到最后一个已更新的项;然后向后逆推前面的索引越来越旧;当你在超过开头时绕过数组末尾时,从数组的末尾继续,反之亦然,当递增索引变量时。这应该不难找到示例。如果需要,rrdtool可以在磁盘文件中使用持久存储执行类似操作。 - tripleee
在一次性操作的情况下,相同的操作更加方便:array_name[length(array_name)] = "item"。 - elixon

3

就像其他人所说的,awk没有提供类似此功能的内置功能。你的“hackish”解决方案可能适用于某些数据集,但对于其他数据集可能不起作用。请考虑您可能会将相同的数组值添加两次,并希望在数组中表示两次。

$ echo 3 | awk 'BEGIN{ a[1]=5; a[2]=12; a[3]=2 }
>   { a[$1] = $1 }
>   END {print length(a) " - " a[3]}'
3 - 3

最好的解决方案可能来自于数组中的数据,但以下是一些想法。
首先,如果您确定索引始终为数字,始终从1开始,并且您永远不会删除数组元素,则triplee的建议A[length(A)+1]="value"可能适合您。但是,如果您删除一个元素,那么下一个写入可能会覆盖您的最后一个元素。
如果您的索引无关紧要,并且您不担心使用长键浪费空间,您可以使用足够长以减少碰撞可能性的随机数。一个快速而简单的选择可能是:
srand()
a[rand() rand() rand()]="value"

记得使用srand()来获得更好的随机化效果,不要依赖rand()来生成真正的随机数。这种方法在很多方面都不是完美的解决方案,但它有一个优点,那就是只需要一行代码。

如果你的键是数字但可能是稀疏的,就像在会破坏tripleee解决方案的示例中一样,你可以在push函数中添加一个小搜索功能:

function push (a, v,     n) {
  n=length(a)+1
  while (n in a) n++
  a[n]=v
}

while循环确保您将分配一个未使用的索引。此函数还与使用非数字索引的数组兼容 - 它会“分配”数值键,但不关心已经存在什么。

请注意,awk无法保证数组中元素的顺序,因此“将项目推入数组的末尾”的想法是错误的。您将向数组添加此元素,但无法保证它在使用for循环遍历时最后出现。

$ cat a
#!/usr/bin/awk -f

function push (a, v,     n) {
  n=length(a)+1
  while (n in a) n++
  a[n]=v
}

{
  push(a, $0)
}

END {
  print "length=" length(a)
  for(i in a) print i " - " a[i]
}

$ printf '3\nfour\ncinq\n' | ./a
length=3
2 - four
3 - cinq
1 - 3

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