0

我正在尝试删除文本中的重复单词。这些文章中描述了相同的问题:使用 sed 和那里删除重复的单词: 使用 SED 删除重复的字符串 但是这些变体对我不起作用。可能是因为我正在使用 GnuWin32

示例我需要什么结果:

输入

One two three bird animal two bird

输出

One two three bird animal
4

3 回答 3

3

我认为这在 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 ( )——它对我们来说是新的。在每一行都被清除,所以我们每行都有唯一的单词,而不是整个文件。!0seen

知道我们没有看到它,我们需要打印它。请注意,单词之间的原始空格丢失了(它没有存储在任何地方)。我们只打印一个空格(但不是在新行的开头,因此是sp变量),然后是新单词。

在 for 循环之后,我们完成了这一行。永远不会有任何尾随空格。(另外,实际的行结尾丢失了,所以我们假设它是\n。如果你想要 DOS 行结尾,请使用\r\n。)

于 2019-12-18T18:07:12.003 回答
2

该工具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

于 2019-12-22T16:15:42.680 回答
1

这可能对您有用(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
于 2019-12-09T12:04:31.940 回答