纯粹的 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
由于这很不明显,让我们分解它的作用并查看每个阶段的输出:
-
删除带有行号的第一列以避免匹配具有重复数字的行号:
$ cut -d ',' -f 2 infile
helloguys.ca
byegirls.com
hellohelloboys.ca
hellobyebyedad.com
letswelcomewelcomeyou.org
letscomewelcomewelyou.org
-
获取所有具有重复序列的行,仅提取该重复序列:
... | grep -Eo '(.*)\1'
ll
hellohello
ll
byebye
welcomewelcome
comewelcomewel
-
获取每一行的长度:
... | awk '{ print length(), $0 }'
2 ll
10 hellohello
2 ll
6 byebye
14 welcomewelcome
14 comewelcomewel
-
按第一列数字降序排列:
...| sort -k 1,1 -nr
14 welcomewelcome
14 comewelcomewel
10 hellohello
6 byebye
2 ll
2 ll
-
为第一列(长度)与第一行具有相同值的所有行打印第二列:
... | awk 'NR==1{prev=$1;print $2;next} $1==prev{print $2;next} {exit}'
welcomewelcome
comewelcomewel
-
将其导入 grep,使用 -f - 参数将标准输入读取为文件:
... | grep -f - infile
5,letswelcomewelcomeyou.org
6,letscomewelcomewelyou.org
限制
虽然这可以处理 cmets 中提到的 bbwelcomewelcome 情况,但它会在重叠模式(例如 welwelcomewelcome)上跳闸,它只能找到 welwel,而不是 welcomewelcome。
更多 awk 的替代解决方案,更少 sort
正如 cmets 中的 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]
}
基准测试
我已经对 cmets 中提到的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 solutionCasimir et Hippolyte:
964438,abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com
real 0m5.249s
user 0m5.234s
sys 0m0.000s
我们从中学到的东西:下次请求 Perl 解决方案 ;)
有趣的是,如果我们知道只有一个最长匹配并相应地简化命令(只是head -1 而不是第一个解决方案的第二个 awk 命令,或者在第二个解决方案中没有使用 awk 跟踪多个最长匹配解决方案),所获得的时间仅在几秒钟的范围内。
便携备注
显然,BSD grep 不能通过 grep -f - 来读取标准输入。在这种情况下,管道的输出直到必须重定向到一个临时文件,然后这个临时文件与grep -f一起使用。