如果发现发生,请删除每一行

时间:2018-08-30 09:02:40

标签: awk sed cat

我有一个具有以下格式内容的文件:

1  6  8
1  6  9
1  12 20
1  6
2  8
2  9
2  12
2  20
2  35

如果要在下一行中找到编号(从第二列或第三列而不是从第一列开始),我想删除所有行,无论它是在第二列还是第三列中,包括找到初始编号的行。 / p>

我应该将此作为输出:

2 35

我尝试使用:

awk '{for(i=2;i<=NF;i++){if($i in a){next};a[$i]}} 1' 

但它似乎不起作用。

怎么了?

5 个答案:

答案 0 :(得分:3)

一次通过awk,将所有记录散列到r[NR],并为在字段a[$i]中看到的值保留另一个数组$2,...NF

awk ' {
    for(i=2;i<=NF;i++)       # iterate fields starting from the second
        if($i in a) {        # if field value was seen before
            delete r[a[$i]]  # delete related record
            a[$i]=""         # clear a
            f=1              # flag up
        } else {             # if it was not seen before
            a[$i]=NR         # add record number to a
            r[NR]=$0
        }
    if(f!=1)                 # if flag was not raised
        r[NR]=$0             # store record on record number
    else                     # if it was raised
        f=""                 # flag down
}
END {
    for(i=1;i<=NR;++i)
        if(i in r)
            print r[i]       # output remaining
}' file

输出:

2  35

答案 1 :(得分:3)

最简单的方法是两次读取文件的两次通过算法。

这个想法是将所有值存储在数组a中,并计算它们出现的次数。如果该值出现2次或更多次,则意味着您已找到了多个条目,而您不应该打印该行。

awk '(NR==FNR){a[$2]++; if(NF>2) a[$3]++; next} 
     (NF==2) && (a[$2]==1);
     (NF==3) && (a[$2]==1 && a[$3]==1)' <file> <file>

在实践中,如果不确定a[var]==1是否在数组中,则应避免使用var之类的东西,因为它将创建该数组元素。但是,由于我们不再增加它,因此可以继续进行。

如果您想通过三个以上的领域来实现同一件事,则可以执行以下操作:

awk '(NR==FNR){for(i=2;i<=NF;++i) a[$i]++; next }
     {for(i=2;i<=NF;++i) if(a[$i]>1) next }
     {print}' <file> <file>

虽然这两种解决方案都两次读取文件,但是您也可以将完整文件存储在内存中,并且只能一次读取文件。但是,这是完全相同的算法:

awk '{for(i=2;i<=NF;++i) a[$i]++; b[NR]=$0}
     END{ for(j=1;j<=NR;++j) {
            $0=b[j];
            for(i=2;i<=NF;++i) if(a[$i]>1) continue
            print $0
          }
         }' <file>

评论:这种单遍解决方案非常简单,并将完整文件存储在内存中。詹姆斯·布朗的解决方案非常聪明。当不再需要它们时,它将从内存中删除它们。简短一点的版本是:

awk '{ for(i=2;i<=NF;++i) if ($i in a) delete b[a[$i]]; else { a[$i]=NR; b[NR]=$0 }}
     END { for(n=1;n<=NR;++n) if(n in b) print b[n] }' <file>

注意:您永远不应该寻求最短的解决方案,而是最易读的解决方案!

答案 2 :(得分:2)

请您尝试以下。

awk '
FNR==NR{
  for(i=2;i<=NF;i++){
    a[$i]++
  }
  next
}
(NF==2 && a[$2]==1) || (NF==3 && a[$2]==1 && a[$3]==1)
'  Input_file  Input_file

输出如下。

2  35

答案 3 :(得分:2)

$ cat tst.awk
NR==FNR {
    cnt[$2]++
    cnt[$3]++
    next
}
cnt[$2]<2 && cnt[$NF]<2

$ awk -f tst.awk file file
2  35

答案 4 :(得分:0)

这可能对您有用(GNU sed):

sed -r 'H;s/^[0-9]+ +//;G;s/\n(.*\n)/\1/;h;$!d;s/^([^\n]*)\n(.*)/\2\n  \1/;:a;/^[0-9]+ +([0-9]+)\n(.*\n)*[^\n]*\1[^\n]*\1[^\n]*$/bb;/^[0-9]+ +[0-9]+ +([0-9]+)\n(.*\n)*[^\n]*\1[^\n]*\1[^\n]*$/bb;/\n/P;:b;s/^[^\n]*\n//;ta;d' file

这不是一个严肃的解决方案,但是它演示了仅使用匹配和替换即可实现的目标。

该解决方案会复制原始文件,同时将每个记录的第二个字段和可能的第三个字段中的所有数字累积在单独的行中,并保留在副本的开头。

在文件末尾,副本的第一行包含所有相关密钥,如果存在重复的密钥,则将删除文件中包含该密钥的任何行。这是通过将键(第一行)移到文件末尾并匹配这些键上每个记录的第二(可能是第三)字段来实现的。