如何从文本文件中删除包含 2 个或更多单词(不以空格分隔)的任何行?

如何从文本文件中删除包含 2 个或更多单词(不以空格分隔)的任何行?

如何从文本文件中删除包含 2 个或更多单词(不以空格分隔)的任何行?

该文件还有这些词的“单一版本”。

例如对于:

alpha
beta
gama
alphabeta
zeta
gamabeta

输出应该是:

alpha
beta
gama
zeta

编辑:请注意,我的文件包含 150 万行

答案1

对于相当短的文件并假设这些行不包含 ERE 运算符:

$ LC_ALL=C grep -vxE "($(paste -sd '|' file)){2,}" file
alpha
beta
gama
zeta

返回不包含 2 个或更多行中任何行的序列的行file

它的工作原理是构建grep如下命令:

LC_ALL=C grep -vxE '(alpha|beta|gama|alphabeta|zeta|gamabeta){2,}' file

对于较大的文件,您将遇到长度或参数+环境(或 Linux 上的单个参数)的限制。这可以通过使用-f -标准输入而不是参数传递正则表达式来解决,但即使如此,您也会遇到正则表达式大小的限制。

使用perl代替grep,我能够处理更大的输入:

perl -le '
  chomp (@words = <>);
  $re = "^(" . join("|", map {qr{\Q$_\E}} @words) . "){2,}\\z";
  for (@words) {print unless m/$re/}' file

(这也避免了上面提到的其他限制)。

无论如何,这将需要很长时间,因为每个单词都需要与其他单词进行比较(可能不止一次)。

答案2

这将打印文件中不是文件中任何两个单词的组合的所有单词:

$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file
alpha
beta
gama
zeta

对于那些喜欢将命令拆分为多行的人:

awk '
    {
    one[NR]=$1
    }

    END{
        for (i=1;i<=length(one);i++)
            for (j=1;j<=length(one);j++)
                two[one[i] one[j]]
        for (i=1;i<=length(one);i++)
            if (!(one[i] in two))
                print one[i]
     }' file

另一个例子

让我们考虑一个包含相似单词的文件,但组合有时出现在各个单词之前:

$ cat file2
alphabeta
alpha
gammaalpha
beta
gamma

运行相同的命令仍然会产生正确的结果:

$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file2
alpha
beta
gamma

怎么运行的

  • one[NR]=$1

    这将创建一个数组one,其中键是行号NR,值是该行上的单词。

  • END{...}

    大括号中的命令在我们读完文件后执行。这些命令由两个循环组成。第一个循环是:

     for (i=1;i<=length(one);i++)
          for (j=1;j<=length(one);j++)
              two[one[i] one[j]]
    

    这将创建一个数组two,其中的键由文件中两个单词的每个组合组成。

    第二个循环是:

      for (i=1;i<=length(one);i++)
          if (!(one[i] in two))
              print one[i]
    

    此循环打印出文件中未作为 array 中的键出现的每个单词two

更短更简单的版本

这个版本使用更短的代码并打印出相同的单词。缺点是不能保证单词的顺序与输入文件中的顺序相同:

$ awk '{one[$1]} END{for (w1 in one) for (w2 in one) two[w1 w2]; for (w in one) if (!(w in two)) print w}' file1
gama
zeta
alpha
beta

更节省内存的方法

对于大文件,上述方法很容易导致内存溢出。在这些情况下,请考虑:

$ sort -u file | awk '{one[$1]} END{for (w1 in one) for (w2 in one) print w1 w2}' >doubles
$ grep -vxFf doubles file
alpha
beta
gama
zeta

这用于sort -u从 file1 中删除任何重复的单词,然后创建一个可能包含双字的文件,称为doubles.然后,grep用于打印file不在 中的行doubles

答案3

<file awk 'NF {print length "\t" $0}' | sort -k1n,1 | cut -f2- |
awk 'NR==1 {min=length}
(l=length) >= 2*min {
  delete k; # clear k array
  k[1];
  while (length(k))
    for (i in k) {
      for (j=l-i+1; j>=min; --j)
        if (substr($0,i,j) in seen) {
          if (i+j-1==l)
            next;
          k[i+j];
        }
      delete k[i];
    }
}
!seen[$0]++'

完全由先前看到的行组成的行将不会被打印。

通过检查子字符串是否存在已见过的字符串来工作。

要求输入文件按行长度从最短到最长排序。awk | sort | cut这样做。

下一个awk程序首先记录最短行的长度(存储为min)。任何长度小于的行都2*min不需要检查其子字符串。相反,它可以添加到seen数组哈希并打印 (!seen[$0]++用作打印非重复项的条件,更多信息:awk '!a[$0]++' 是如何工作的?)。min也可以用作检查子字符串时的截止长度。

当扫描行查找子串时,必须记录任何新的可能的起始位置。这是使用数组k来存储这些偏移量来完成的。扫描子字符串并检查它们是否作为数组的哈希存在seen。当找到看到的字符串时:

  • 如果子字符串位于行尾,则转到next输入行。该行不会打印或添加到可见数组中。
  • 否则,添加下一个起始位置k并继续扫描更多子字符串。
  • 只要找到新的起始位置,就继续尝试 ( while (length(k)))。
  • 如果上面的循环没有前进到下一行,则该行将添加到seen数组哈希中(如果尚未看到,则打印)。

答案4

awk '{for (i in a) if (index($0,i)) next; print; a[$0]}' file

相关内容