使用awk查找包含最长重复单词的域名

时间:2016-02-15 23:25:22

标签: regex bash awk

例如,假设有一个名为domains.csv的文件,其中包含以下内容:

1,helloguys.ca
2,byegirls.com
3,hellohelloboys.ca
4,hellobyebyedad.com
5,letswelcomewelcomeyou.org

我正在尝试使用linux awk regex表达式来查找包含最长重复 1 字的行,所以在这种情况下,它将返回行

5,letswelcomewelcomeyou.org

我该怎么做?

1 含义“立即重复”,即abcabc,但不是abcXabc

2 个答案:

答案 0 :(得分:6)

纯awk实现会相当啰嗦,因为awk正则表达式没有反向引用,其使用简化了方法。

我已经为示例输入文件添加了一行,用于多个最长单词的情况:

1,helloguys.ca
2,byegirls.com
3,hellohelloboys.ca
4,hellobyebyedad.com
5,letswelcomewelcomeyou.org
6,letscomewelcomewelyou.org

这样可以获得具有最长重复序列的行:

cut -d ',' -f 2 infile | grep -Eo '(.*)\1' |
awk '{ print length(), $0 }' | sort -k 1,1 -nr |
awk 'NR==1 {prev=$1;print $2;next} $1==prev {print $2;next} {exit}' | grep -f - infile

由于这是非常明显的,所以让我们分解它的作用并查看每个阶段的输出:

  1. 删除第一列的行号,以避免匹配重复数字的行号:

    $ cut -d ',' -f 2 infile
    helloguys.ca
    byegirls.com
    hellohelloboys.ca
    hellobyebyedad.com
    letswelcomewelcomeyou.org
    letscomewelcomewelyou.org
    
  2. 获取具有重复序列的所有行,只提取重复序列:

    ... | grep -Eo '(.*)\1'
    ll
    hellohello
    ll
    byebye
    welcomewelcome
    comewelcomewel
    
  3. 获取每一行的长度:

    ... | awk '{ print length(), $0 }'
    2 ll
    10 hellohello
    2 ll
    6 byebye
    14 welcomewelcome
    14 comewelcomewel
    
  4. 按第一列排序,数字,降序:

    ...| sort -k 1,1 -nr
    14 welcomewelcome
    14 comewelcomewel
    10 hellohello
    6 byebye
    2 ll
    2 ll
    
  5. 为第一列(长度)与第一行具有相同值的所有行打印其中第二列:

    ... | awk 'NR==1{prev=$1;print $2;next} $1==prev{print $2;next} {exit}'
    welcomewelcome
    comewelcomewel
    
  6. 将此管道传输到grep,使用-f -参数将stdin作为文件读取:

    ... | grep -f - infile
    5,letswelcomewelcomeyou.org
    6,letscomewelcomewelyou.org
    
  7. <强>限制

    虽然这可以处理评论中提到的bbwelcomewelcome案例,但它会重叠welwelcomewelcome等重叠模式,只会找到welwel,而不是welcomewelcome

    更多awk的替代解决方案,更少sort

    正如tripleee在评论中指出的那样,这可以简化为跳过sort步骤并将两个awk步骤和sort步骤合并为一个awk步骤,可能会改进性能:

    $ cut -d ',' -f 2 infile | grep -Eo '(.*)\1' |
    awk '{if (length()>ml) {ml=length(); delete a; i=1} if (length()>=ml){a[i++]=$0}}
    END{for (i in a){print a[i]}}' |
    grep -f - infile
    

    让我们更详细地看一下这个awk步骤,为了清晰起见,使用扩展的变量名称:

    {
        # New longest match: throw away stored longest matches, reset index
        if (length() > max_len) {
            max_len = length()
            delete arr_longest
            idx = 1
        }
    
        # Add line to longest matches
        if (length() >= max_len)
            arr_longest[idx++] = $0
    }
    
    # Print all the longest matches
    END {
        for (idx in arr_longest)
            print arr_longest[idx]
    }
    

    <强>基准

    我在评论中提到的top one million domains file上计算了两个解决方案:

    • 第一个解决方案(sort和两个awk步骤):

      964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
      
      real    1m55.742s
      user    1m57.873s
      sys     0m0.045s
      
    • 第二个解决方案(只有一个awk步骤,没有sort):

      964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
      
      real    1m55.603s
      user    1m56.514s
      sys     0m0.045s
      
    • Perl solution Casimir et Hippolyte

      964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
      
      real    0m5.249s
      user    0m5.234s
      sys     0m0.000s
      

    我们从中学到了什么:下次请求Perl解决方案;)

    有趣的是,如果我们知道只有一个最长的匹配并相应地简化命令(仅head -1而不是第一个解决方案的第二个awk命令,或者没有跟踪与awk的多个最长匹配第二个解决方案),获得的时间仅在几秒钟的范围内。

    可移植性评论

    显然,BSD grep无法从{stin}读取grep -f -。在这种情况下,管道的输出直到必须重定向到临时文件,然后此临时文件与grep -f一起使用。

答案 1 :(得分:6)

perl的一种方式:

perl -F, -ane 'if (@m=$F[1]=~/(?=(.+)\1)/g) {
    @m=sort { length $b <=> length $a} @m;
    $cl=length @m[0];
    if ($l<$cl) { @res=($_); $l=$cl; } elsif ($l==$cl) { push @res, ($_); }
}
END { print @res; }' file

我们的想法是为第二个字段中的每个位置找到所有最长重叠的重复字符串,然后对匹配数组进行排序,最长的子字符串成为数组中的第一个项目(@m[0])。

完成后,将当前重复子字符串($cl)的长度与存储长度(前一个最长子字符串)进行比较。当前重复的子串长于存储的长度时,结果数组将被当前行覆盖,当长度相同时,当前行被推入结果数组。

细节:

命令行选项:

-F,将字段分隔符设置为,
-anee执行以下代码,n一次读取一行,并使用定义的FS将其内容放入$_a autosplit中,并将字段放在@F数组中)

模式:

/
(?=         # open a lookahead assertion
    (.+)\1  # capture group 1 and backreference to the group 1
)           # close the lookahead
/g # all occurrences 

这是一个众所周知的模式,可以在字符串中查找所有重叠的结果。我们的想法是使用前瞻不消耗字符的事实(前瞻只意味着“检查此子模式是否跟在当前位置”,但它与任何字符都不匹配)。要获得前瞻中匹配的字符,您需要的只是一个捕获组。 由于前瞻不匹配,因此在每个位置测试模式(并不关心字符是否已在组1中捕获过。)