使用 sed GnuWin32 删除一行中的重复单词

Remove duplicate words in a line with sed GnuWin32

我正在尝试删除文本中的重复字词。这些文章中描述的相同问题:Remove duplicate words in a line with sed 那里: Removing duplicate strings with SED 但是这些变体对我不起作用。可能是因为我使用的是 GnuWin32

例如我需要的结果:

输入

One two three bird animal two bird

输出

One two three bird animal

这可能适合您 (GNU sed):

sed -E ':a;s/\<((\S+)\>.*)\s\<\>//gi;ta' file

匹配任何单词并删除前面的白色 space 及其重复项。重复。

N.B。正则表达式删除重复项而不考虑大小写。如果要将 Oneone 分开处理,请使用:

sed -E ':a;s/\<((\S+)\>.*)\s\<\>//g;ta' file

我认为这在 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 擅长列式数据。默认情况下,它将每一行的文本划分为由连续的白色 space 分隔的字段(因此给定 hello world,我们得到 = "hello" = "world")。特殊的 NF 变量是它找到的字段数,因此 for (i=1; i<=NF; i++) 遍历每个字段(单词)作为 i 其值为 $i.

我在这里使用关联数组(又名字典或散列)。索引 $i 处的 seen 数组(当前单词)从零开始(未初始化)。我们增加它,但就像 C 一样,awk 使用 x++ 增加 x 但 return 它的原始值(对比 ++x 增加和 returns增值)。因此,当我们还没有在这个词处递增数组时,!seen[$i]++ 为真 (!0)——它对我们来说是新的。 seen 在每一行都被清除,因此我们每行都有唯一的单词而不是整个文件。

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

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

工具sed并不是真正为这项工作设计的。 sed 只有两种记忆形式,pattern-space 和 hold-space,无非就是它能记住的两个简单的字符串。每次对这样的内存块进行操作时,都必须重写整个内存块并重新分析它。另一方面,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

这个可以用,因为你用的CygWin到底是GNU,所以我们可以用RS的扩展名来做正则表达式或者多字符值。

如果您想要区分大小写,您可以将 s[$i] 替换为 s[tolower($i)]

这样的句子还有问题
"There was a horse in the bar, it ran out of the bar."

单词bar可以在这里匹配,但是,.使它不匹配。这可以通过以下方式解决:

awk 'BEGIN{RS=ORS="\r\n"; ere="[,.?:;27]"}
     {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