例如,假设有一个名为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
。
答案 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
由于这是非常明显的,所以让我们分解它的作用并查看每个阶段的输出:
删除第一列的行号,以避免匹配重复数字的行号:
$ 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 -
参数将stdin作为文件读取:
... | grep -f - infile
5,letswelcomewelcomeyou.org
6,letscomewelcomewelyou.org
<强>限制强>
虽然这可以处理评论中提到的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,
将字段分隔符设置为,
-ane
(e
执行以下代码,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中捕获过。)