循环浏览时在awk中删除数组元素:始终安全吗?

时间:2019-05-24 07:58:37

标签: arrays loops awk semantics

这是一个非常棘手的问题:我想知道循环迭代器for (k in array)的确切语义是什么:我知道我们对数组元素的扫描顺序没有太多控制,但是我想要知道删除这样一个循环主体中的数组元素是否总是安全的(即由某些POSIX规范保证)。我的意思是,是否保证该循环中的后续迭代将正常运行,而无需跳过任何元素或不删除已删除的元素?

下面是一个最小的示例,其中我们忽略了输入中以大写字母“ A”开头的所有名称。它似乎可以在我的GNU Awk 4.2.1上很好地工作,但是我不确定它是否在所有awk实现中都是完全可移植且安全的。有什么想法吗?谢谢!

echo -e "Alberto\n Adam\n Payne\n Kristell\n John\n\
   Arjuna\n Albert\n Me\n You\n Toto\n Auntie\n Terribel" | 
awk '{ names[NR] = $1 } 
     END { for (k in names)
             if (substr(names[k], 1, 1) == "A") delete names[k];
           for (k in names) print names[k] }'

3 个答案:

答案 0 :(得分:1)

看起来应该很安全:

https://www.gnu.org/software/gawk/manual/html_node/Delete.html

  

8.4删除语句   要删除数组的单个元素,请使用delete语句:

delete array[index-expression] 
  

删除数组元素后,   元素曾经拥有的任何值都不再可用。好像   元素从未被引用或未被赋予值。的   以下是删除数组中元素的示例:

for (i in frequencies)
    delete frequencies[i]

如果可以安全地使用遍历数组所有元素的数组删除数组中的所有元素,则您的代码同样应该是安全的。


这是for循环上的另一个资源:https://www.gnu.org/software/gawk/manual/html_node/Scanning-an-Array.html#Scanning-an-Array

  

此语句访问数组元素的顺序由awk内的数组元素的内部排列确定,在标准awk中无法控制或更改。如果将新元素添加到循环主体中的by语句数组中,则可能导致问题。 for循环是否会到达它们是不可预测的。同样,在循环内部更改var可能会产生奇怪的结果。最好避免这种事情。

没有提及删除。

答案 1 :(得分:1)

是,不是。删除条目是“安全的”,因为删除后条目将不存在,但是不能安全地假设您在循环迭代时删除索引后不会再点击该索引。

The POSIX spec不能说:

the following code deletes an entire array:

for (index in array)
    delete array[index]

如果这样做可能会跳过索引,并且这样做:

for (index in arrayA) {
    if (index in arrayB) {
        print "Both:", index
        delete arrayA[index]
        delete arrayB[index]
    }
}

for (index in arrayA)
    print "A only:", index

for (index in arrayB)
    print "B only:", index

是一个极端惯用语,用于查找值中包含哪些值,如果在这种情况下该方法不是“安全”的,那将是行不通的。

并不意味着您可以假定在删除数组索引后不会在循环中删除该数组索引,因为awk是否可以找出所有在进入循环之前或执行期间要访问的数组索引取决于实现。例如,GNU awk在进入循环之前确定它将要访问的所有索引,因此您将获得以下行为:数组在delete a[3]之后缩短了1个元素,但删除了索引{{ 1}}仍在先前删除它的循环中被访问:

3

但不是所有的awks都这样做,例如BWK awk / nawk没有,MacOS / BSD awk也没有:

$ gawk 'BEGIN{split("a b c d e",a);
    for (i in a) {print length(a), i, a[i]; delete a[3]} }'
5 1 a
4 2 b
4 3
4 4 d
4 5 e

gawk行为与上述其他awk中的行为相同:

$ awk 'BEGIN{split("a b c d e",a);
    for (i in a) {print length(a), i, a[i]; delete a[3]} }'
5 2 b
4 4 d
4 5 e
4 1 a

我在上面使用了一个未分配的变量$ awk 'BEGIN{split("a b c d e",a); for (i in a) b[i]; for (i in b) { print length(a), i, (i in a ? a[i] : x); delete a[3]} }' 5 2 b 4 3 4 4 d 4 5 e 4 1 a 而不是x来准确描述删除后""的零或零性质,但这实际上并不重要情况,因为我们还是将其打印为“”。

因此,无论您使用哪种awk,一旦退出上述循环a[3],例如再次使用GNU awk:

a[3]

请注意,在以上脚本中,$ gawk 'BEGIN{split("a b c d e",a); for (i in a) {print length(a), i, a[i]; delete a[3]} print "---"; for (i in a) {print i, a[i]} }' 5 1 a 4 2 b 4 3 4 4 d 4 5 e --- 1 a 2 b 4 d 5 e 实际上是在第一个循环中重新创建的,因为a[3]a[i]时访问了i,但随后发生了3因为每个索引都会再次删除它。如果我们仅在delete a[3]i时进行删除,那么我们会看到1存在,但在循环后包含零或空值:

a[3]

要了解为什么在循环之前预先确定将要访问的索引的gawk方法比尝试在循环中即时确定索引要好,请考虑一下这段代码,该代码试图添加3个新元素到循环内的数组:

$ gawk 'BEGIN{split("a b c d e",a);
        for (i in a) {print length(a), i, a[i]; if (i==1) delete a[3]}
        print "---";
        for (i in a) {print i, a[i]} }'
5 1 a
4 2 b
4 3
5 4 d
5 5 e
---
1 a
2 b
3
4 d
5 e

使用gawk时,输出和最终结果都是可预测的,并且可以根据需要:

$ cat tst.awk
BEGIN {
    split("a b c",a)
    for (i in a) {
        j=i+100
        a[j] = "foo" j
        print length(a), i, a[i]
    }
    print "---"
    for (i in a) {
        print i, a[i]
    }
}

使用MacOS / BSD awk时(忽略顺序,只需查看数组的长度和索引的值):

$ gawk -f tst.awk
4 1 a
5 2 b
6 3 c
---
6 1 a
6 2 b
6 3 c
6 101 foo101
6 102 foo102
6 103 foo103

显然是混乱的,因为它试图在循环时访问循环中添加的索引,但是成功有限(大概是由于哈希表中新索引与先前访问的哈希表条目的顺序有关),这很幸运否则我们将陷入无限循环。

要从MacOS / BSD awk等获得有用的结果,您再次需要在循环之前将预定的索引保存在新数组中,如上所示:

$ awk -f tst.awk
4 2 b
5 3 c
6 102 foo102
7 103 foo103
8 202 foo202
9 203 foo203
10 302 foo302
11 1 a
---
11 303 foo303
11 2 b
11 3 c
11 402 foo402
11 101 foo101
11 102 foo102
11 103 foo103
11 202 foo202
11 203 foo203
11 302 foo302
11 1 a

哦,还有$ cat tst.awk BEGIN { split("a b c",a) for (i in a) { b[i] } for (i in b) { j=i+100 a[j] = "foo" j print length(a), i, a[i] } print "---" for (i in a) { print length(a), i, a[i] } } $ awk -f tst.awk 4 2 b 5 3 c 6 1 a --- 6 2 b 6 3 c 6 101 foo101 6 102 foo102 6 103 foo103 6 1 a -使用GNU awk,您可以通过设置I know we don't have much control on the order in which the array elements are scanned来精确地控制它,请参见https://www.gnu.org/software/gawk/manual/gawk.html#Controlling-Scanning。例如:

PROCINFO["sorted_in"]

答案 2 :(得分:0)

通常,在数组/容器上进行迭代时修改数组/容器是不安全的,被认为是不好的做法。 Java语言为此提供了特殊的例外。

一种更安全的方法是遍历数组并创建一个包含要删除索引的数组。

赞:

 for (k in names) 
     if (substr(names[k], 1, 1) == "A") deletions[++i] = k;
 for (k in deletions)
     delete names[deletions[k]];
 for (k in names) print names[k] }'