我正在尝试删除文本中的重复单词。这些文章中描述了相同的问题:使用 sed 和那里删除重复的单词: 使用 SED 删除重复的字符串 但是这些变体对我不起作用。可能是因为我正在使用 GnuWin32
示例我需要什么结果:
输入
One two three bird animal two bird
输出
One two three bird animal
我正在尝试删除文本中的重复单词。这些文章中描述了相同的问题:使用 sed 和那里删除重复的单词: 使用 SED 删除重复的字符串 但是这些变体对我不起作用。可能是因为我正在使用 GnuWin32
示例我需要什么结果:
输入
One two three bird animal two bird
输出
One two three bird animal
我认为这在 awk 中会快得多。
这应该适用于任何平台,但我尚未在 Windows 上验证它:
awk '{
sp = "";
delete seen;
for (i=1; i<=NF; i++) if (!seen[$i]++) { printf "%s%s", sp, $i; sp = " "; }
printf "\n";
}' file
(随意将其压缩为一行,它会正常工作。)
AWK 擅长柱状数据。默认情况下,它将每一行的文本划分为由连续空格分隔的字段(因此给定hello world
,我们得到$1 = "hello"
和$2 = "world"
)。特殊NF
变量是它找到的字段数,因此for (i=1; i<=NF; i++)
迭代每个字段(单词),i
其值为$i
.
我在这里使用关联数组(也称为字典或哈希)。索引处的seen
数组$i
(当前字)从零开始(未初始化)。我们递增它,但就像 C 一样,awk 使用x++
递增x
但返回其原始值(与++x
递增并返回递增值相反)。因此,当我们还没有在这个词处增加数组时,它!seen[$i]++
是 true ( )——它对我们来说是新的。在每一行都被清除,所以我们每行都有唯一的单词,而不是整个文件。!0
seen
知道我们没有看到它,我们需要打印它。请注意,单词之间的原始空格丢失了(它没有存储在任何地方)。我们只打印一个空格(但不是在新行的开头,因此是sp
变量),然后是新单词。
在 for 循环之后,我们完成了这一行。永远不会有任何尾随空格。(另外,实际的行结尾丢失了,所以我们假设它是\n
。如果你想要 DOS 行结尾,请使用\r\n
。)
该工具sed
并不是真正为这项工作而设计的。sed 只有两种形式的记忆,模式空间和保持空间,它们只不过是它可以记住的两个简单的字符串。每次对这样的内存块进行操作时,都必须重写整个内存块并重新分析它。另一方面,Awk 在这里有更多的灵活性,并且可以更容易地操作有问题的行。
awk '{delete s}
{for(i=1;i<=NF;++i) if(!(s[$i]++)) printf (i==1?"":OFS)"%s",$i}
{printf ORS}' file
但是由于您在 Windows 机器上工作,这也意味着您有 CRLF 行尾。这可能会对最后一个条目产生轻微的问题。如果该行显示:
foo bar foo
awk 会将其读作
foo bar foo\r
因此,由于 CR,最后一个 foo 将与第一个 foo 不匹配。
现在更正如下:
awk 'BEGIN{RS=ORS="\r\n"}
{delete s}
{for(i=1;i<=NF;++i) if(!(s[$i]++)) printf (i==1?"":OFS)"%s",$i}
{printf ORS}' file
这可以使用,因为您使用最终是 GNU 的 CygWin,因此我们可以使用扩展 on ofRS
作为正则表达式或多字符值。
如果你想要区分大小写,你可以s[$i]
用s[tolower($i)]
.
像这样的句子仍然存在问题
"There was a horse in the bar, it ran out of the bar."
这个词bar
可以在这里匹配,但,
and.
使它不匹配。这可以通过以下方式解决:
awk 'BEGIN{RS=ORS="\r\n"; ere="[,.?:;\042\047]"}
{delete s}
{for(i=1;i<=NF;++i) {
key=tolower($i); sub("^" ere,"",key); sub(ere "$","",key)
if(!(s[key]++)) printf (i==1?"":OFS)"%s",$i
}
}
{printf ORS}' file
这本质上是相同的,但删除了单词开头和结尾的标点符号。标点符号列于ere
这可能对您有用(GNU sed):
sed -E ':a;s/\<((\S+)\>.*)\s\<\2\>/\1/gi;ta' file
匹配任何单词并删除前面的空格及其重复项。重复。
注意 regexp 删除重复项而不考虑大小写。如果要One
分开处理one
使用:
sed -E ':a;s/\<((\S+)\>.*)\s\<\2\>/\1/g;ta' file