【问题标题】:Printing lines with duplicate words打印带有重复单词的行
【发布时间】:2017-10-04 06:10:48
【问题描述】:

我正在尝试打印所有可以包含两次或多次相同单词的行

例如使用这个输入文件:

cat dog cat
dog cat deer
apple peanut banana  apple
car bus train plane
car train car train

输出应该是

cat dog cat
apple peanut banana  apple
car train car train.

我已经尝试过这段代码,它可以工作,但我认为必须有更短的方法。

awk '{ a=0;for(i=1;i<=NF;i++){for(j=i+1;j<=NF;j++){if($i==$j)a=1} } if( a==1 ) print $0}'

稍后我想找到所有这些重复的单词并删除所有重复的条目,除了第一次出现。

所以输入:

cat dog cat lion cat 
dog cat deer
apple peanut banana  apple
car bus train plane
car train car train

期望的输出:

cat dog lion 
dog cat deer
apple peanut banana  
car bus train plane
car train

【问题讨论】:

  • 我正在寻找正则表达式解决方案,有什么问题?
  • regex 也用于其他语言,例如 perl 和 ruby​​,但我想在 awk、sed 和 grep 中坚持使用 regex,所以我放了这些标签。
  • 祝你好运在awk 中找到答案,regex

标签: awk sed grep


【解决方案1】:

你可以使用这个 GNU sed 命令:

sed -rn '/(\b\w+\b).*\b\1\b/ p' yourfile
  • -r 激活扩展 re 和 n 禁用每行的隐式打印
  • p 命令然后只打印与前面的 re 匹配的行(在斜杠内):
    • \b\w+\b are words : an nonemtpy sequence of word charactes (\w) between word boundaries (\b`),这些是 GNU 扩展
    • 这样的词“存储”在\1 中以供以后重复使用,因为使用了括号
    • 然后我们尝试再次将这个词与\b\1\b 匹配,并在这两个位置之间添加一些可选的(.*)。
    • 这就是全部技巧:匹配一些东西,把它放在括号中,这样你就可以在\1 的同一个 re 中重复使用它

要回答问题的第二部分,删除第一部分之后的双字,但打印所有行(仅修改带有双字的行),您可以使用一些 sed s 魔术:

sed -r ':A s/(.*)(\b\w+\b)(.*)\b\2\b(.*)/\1\2\3\4/g; t A ;'
  • 这里我们再次使用反向引用技巧。
  • 但我们必须考虑双字之前、之间和之后的内容,因此我们在s 命令的匹配部分有一个\2,而在替换部分我们有其他反向引用。
  • 请注意,只有 \2 在匹配部分没有括号,我们在替换中使用了所有组,因此我们有效地删除了该对的第二个单词。
  • 对于我们需要循环的单词的更多重复:
    • :A 是一个标签
    • 如果在最后一个 s comamnd 中完成了替换,t A 会跳转到标签
    • 这会在s 周围构建一个“while 循环”来删除其他重复项

【讨论】:

  • 谢谢拉尔斯,这有助于我认为第三点中的 \b\w+\w 你的意思是 \b\w+\b 。您能否就问题的第二部分“删除所有重复的单词,但第一次出现?
  • @user3369871 感谢您的更正。请为问题的第二部分添加所需的输出。例如。没有双打的行应该怎么办,是否应该再次打印?
  • 刚刚意识到你发布的逻辑和我一样。从来不知道在扩展的 posix 正则表达式中可以进行反向引用。
  • 刚刚检查了egrep。也可以。
  • @user3369871 更新了第二部分的答案。比尔·卡尔文斯对杰米·扎温斯基的引用是如此真实:从简单易懂的事情开始,RE 迅速演变为难以理解的事情:)
【解决方案2】:

这是一个只打印包含重复单词的行的解决方案。

awk '{
  delete seen
  for (i=1;i<=NF;++i) {
    if (seen[$i]) { print ; next }
    seen[$i] = 1 
  }
}'

这是删除第一个之后的重复单词的解决方案。

awk '{
  delete seen
  for (i=1;i<=NF;++i) {
    if (seen[$i]) { continue }
    printf("%s ", $i);
    seen[$i] = 1 
  }
  print "";
}'

你的评论...

有些人在遇到问题时会想“我知道,我会使用正则表达式”。现在他们有两个问题。 ——杰米·扎温斯基,1997

【讨论】:

  • 我实际上正在寻找可以做到这一点的正则表达式。
  • @EdMorton,谢谢,这些都是很好的建议,我已经编辑了以上内容。
  • @EdMorton,谢谢,但我对这个问题失去了兴趣,因为 OP 似乎并不欣赏答案。
  • 是的,我也是,刚刚删除了我的答案,因为这不是 OP 想要的。
【解决方案3】:

使用egrep,您可以使用所谓的反向引用:

egrep '(\b\w+\b).*\b\1\b' file

(\b\w+\b) 匹配捕获组 1 中单词边界处的单词。\1 引用模式中匹配的单词。

【讨论】:

    【解决方案4】:

    我将展示 Perl 中的解决方案,因为它可能是最灵活的文本解析工具,尤其是在正则表达式方面。

    检测重复项

    perl -ne 'print if m{\b(\S+)\b.*?(\b\1\b)}g' file
    

    在哪里

    • -n 使 Perl 为每个输入行执行通过 -e 传递的表达式;
    • \b 匹配单词边界;
    • \S+ 匹配一个或多个非空格字符;
    • .*? 是非 greedy 匹配零个或多个字符;
    • \1 是第一组的backreference,即单词\S+
    • g 全局匹配字符串中重复的模式。

    删除重复项

    perl -pe '1 while (s/\b(\S+)\b.*?\K(\s\1\b)//g)' file
    

    在哪里

    • -p 导致 Perl 打印行 ($_),如 sed;
    • 只要替换替换了某些东西,1 while 循环就会运行;
    • \K 保持与前一个表达式匹配的部分;

    重复的单词 (\s\1\b) 被替换为空字符串 (//g)。

    为什么选择 Perl?

    众所周知,Perl 正则表达式非常灵活,而 Perl 中的正则表达式实际上不仅仅是正则表达式。例如,您可以使用 /e 修饰符将 Perl 代码嵌入到 substitution 中。您可以使用/x 修饰符,它允许以更易读的格式编写正则表达式,甚至可以在其中使用 Perl cmets,例如:

    perl -pe '1 while (
      s/            # Begins substitution: s/pattern/replacement/flags
      \b (\S+) \b   # A word
      .*?           # Ungreedy pattern for any number of characters
      \K            # Keep everything that matched the previous patterns
      (             # Group for the duplicate word:
        \s          #   - space
        \1          #   - backreference to the word
        \b          #   - word boundary
      )
      //xg
    )' file
    

    你应该注意到了,\Kanchor 非常方便,但是is not available in many popular tools 包括awkbashsed em>。

    【讨论】:

      猜你喜欢
      • 2015-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-02
      相关资源
      最近更新 更多