【问题标题】:Remove duplicate words in a line with sed GnuWin32使用 sed GnuWin32 删除一行中的重复单词
【发布时间】:2020-04-02 08:22:19
【问题描述】:

我正在尝试删除文本中的重复单词。这些文章中描述了相同的问题: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 操作,您就会明白这是一个没有任何有效答案的新问题!

标签: regex sed gnuwin32


【解决方案1】:

我认为这在 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&lt;=NF; i++) 将每个字段(单词)迭代为i,其值为$i

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

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

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

【讨论】:

    【解决方案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,因此我们可以使用 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="[,.?:;\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

    【讨论】:

    • 使用像“00:00:02.170 –> 00:00:06.915 foo bar foo foo bar”这样的行,这个 awk 正确地删除了重复的 foo。它还删除了第二个时间戳,我该如何防止这种情况? @kvantour 新输出为“00:00:02.170 foo bar foo bar”
    • @ladyskynet 我无法重现此内容。你能用cat -vET准确地告诉我那条线吗?
    • 我正在测试的命令是: echo "00:00:02.170 --> 00:00:06.915 Forward I I mean, I will be" | awk '{删除 s} {for(i=1;i
    • cat -vet of the line 给了我这个:00:00:02.170 --> 00:00:06.915^M$ Forward 我的意思是,我会是^M$ @kvantour
    • @ladyskynet,我可以重现这个。这样做的原因是我们删除了所有非字母字符。这包括数字。因此,字符串00:00:02.17000:00:06.915 是等价的。如果你不想要这个,你必须用[^a-z0-9] 更新子命令。但是现在我看这个,这不是一个好方法。此外,假设文章中的最后一种方法适用于句子,而不是与日志相关的字符串。但这可以更新。让我检查一下。
    【解决方案3】:

    这可能对你有用(GNU sed):

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

    匹配任何单词并删除前面的空格及其重复项。重复。

    注意正则表达式删除重复而不考虑大小写。如果您想将Oneone 分开处理,请使用:

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

    【讨论】:

    • 代码对小文件是正确的,但对大文件来说太慢了。我尝试使用此代码,但程序滞后 20 分钟以上,仍然滞后,没有任何结果。我有 60.000 + 字和 310kb 大小的文件
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-28
    • 2018-11-20
    • 2018-07-27
    • 1970-01-01
    • 2012-02-06
    • 2020-04-18
    • 2019-03-28
    相关资源
    最近更新 更多