如何从文本文件中删除包含 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