如何 grep/perl/awk 重叠正则表达式
How to grep/perl/awk overlapping regex
尝试将字符串通过管道传输到 grep/perl 正则表达式中以提取重叠的匹配项。目前,结果似乎只拉出没有任何“回顾”的顺序匹配:
尝试使用 egrep(在 GNU 和 BSD 上):
$ echo "bob mary mike bill kim jim john" | egrep -io "[a-z]+ [a-z]+"
bob mary
mike bill
kim jim
尝试使用 perl 风格的 grep (-P):
$ echo "bob mary mike bill kim jim john" | grep -oP "()[a-z]+ [a-z]+"
bob mary
mike bill
kim jim
尝试使用 awk 仅显示第一个匹配项:
$ echo "bob mary mike bill kim jim john" | awk 'match([=13=], /[a-z]+ [a-z]+/) {print substr([=13=], RSTART, RLENGTH)}'
bob mary
我希望通过简单的 bash 管道命令看到的重叠结果是:
bob mary
mary mike
mike bill
bill kim
kim jim
jim john
有什么想法吗?
Lookahead 是你的朋友
echo "bob mary mike bill kim jim john" |
perl -wnE'say " " while /(\w+)\s+(?=(\w+))/g'
重点是前瞻作为“零宽度断言”不会消耗任何东西——但它仍然允许我们捕获其中的模式。
因此,当正则表达式引擎匹配一个词和 spaces ((\w+)\s+
) 时,吞噬它们,然后停在那里并“向前看”,只是为了“断言”所寻求的模式在那里;它不会从最后一个 space 和下一个 \w
之间的位置移动,不会像他们说的那样“消耗”下一个单词。
很高兴我们还可以捕获“看到”的模式,即使它没有被消耗!所以我们得到了我们的 </code> 和 <code>
,两个词。
然后,由于 /g
修饰符,引擎继续寻找另一个词+spaces,后面还有另一个词。下一个词是我们前瞻性发现的词——所以现在一个词被消耗了,但下一个词是“寻找”(并捕获)的。等等
您也可以使用awk
awk '{for(i=1;i<NF;i++) print $i,$(i+1)}' <<< 'bob mary mike bill kim jim john'
见online demo。此解决方案遍历所有以白色 space 分隔的字段并打印当前字段 ($i
) + 字段分隔符(此处为 space)+ 后续字段值 ($(i+1)
)。
或者,另一个 perl
解决方案,它使用 very common technique 捕获正前瞻内的重叠模式:
perl -lane 'while (/(?=\b(\p{L}+\s+\p{L}+))/g) {print }' <<< 'bob mary mike bill kim jim john'
见online demo。 详情:
(?=
- 正面前瞻的开始
\b
- 单词边界
(\p{L}+\s+\p{L}+)
- 捕获组 1:一个或多个字母,一个或多个白色space,一个或多个字母
)
- 前瞻结束。
此处仅打印第 1 组值 ({print }
)。
性能考虑
至于这里的 Perl 解决方案,我的是最慢的,而 Timur 是最快的,但是,awk
解决方案比任何 Perl 解决方案都快。结果:
# ./wiktor_awk.sh
real 0m17.069s
user 0m12.264s
sys 0m5.314s
# ./timur_perl.sh
real 0m18.201s
user 0m15.612s
sys 0m6.139s
# ./zdim.sh
real 0m23.559s
user 0m19.883s
sys 0m7.359s
# ./wiktor_perl.sh
real 2m12.528s
user 1m52.857s
sys 0m20.201s
注意我为每个解决方案创建了 *.sh 文件,例如
#!/bin/bash
N=10000
time(
for i in $(seq 1 $N); do
<SOLUTION_HERE> &>/dev/null;
done)
和运行 for f in *.sh; do chmod +x "$f"; done
(借自here)。
使用下面的 Perl 单行代码,避免前瞻(它仍然是你的朋友):
对于以空格分隔的单词:
echo "bob mary mike bill kim jim john" | perl -lane 'print "$F[$_] $F[$_+1]" for 0..($#F-1);'
对于在 Perl 中定义为 \w+
的单词,由非单词字符分隔 \W+
:
echo "bob.mary,mike'bill kim jim john" | perl -F'/\W+/' -lane 'print "$F[$_] $F[$_+1]" for 0..($#F-1);'
Perl 单行代码使用这些命令行标志:
-e
: 告诉 Perl 查找内联代码,而不是在文件中。
-n
:一次循环输入一行,默认分配给 $_
。
-l
: 在执行内联代码之前去除输入行分隔符(默认情况下在 *NIX 上为 "\n"
),并在打印时附加它。
-a
: 在空格或 -F
选项中指定的正则表达式上将 $_
拆分为数组 @F
。
-F'/\W+/'
:在 \W+
(一个或多个非单词字符)上拆分为 @F
,而不是在空格上拆分。
$#F
:数组 @F
的最后一个索引,输入行被分割成该索引。
0..($#F-1)
:索引(数字)的范围,从数组的第一个(0
)到倒数第二个($#F-1
)索引 @F
.
$F[$_]
和$F[$_+1]
:数组@F
的两个连续元素,索引分别为$_
和$_+1
。
另请参见:
perldoc perlrun
: how to execute the Perl interpreter: command line switches
perldoc perlre
: Perl regular expressions (regexes)
perldoc perlre
: Perl regular expressions (regexes): Quantifiers; Character Classes and other Special Escapes; Assertions; Capture groups
perldoc perlrequick
: Perl regular expressions quick start
尝试将字符串通过管道传输到 grep/perl 正则表达式中以提取重叠的匹配项。目前,结果似乎只拉出没有任何“回顾”的顺序匹配:
尝试使用 egrep(在 GNU 和 BSD 上):
$ echo "bob mary mike bill kim jim john" | egrep -io "[a-z]+ [a-z]+"
bob mary
mike bill
kim jim
尝试使用 perl 风格的 grep (-P):
$ echo "bob mary mike bill kim jim john" | grep -oP "()[a-z]+ [a-z]+"
bob mary
mike bill
kim jim
尝试使用 awk 仅显示第一个匹配项:
$ echo "bob mary mike bill kim jim john" | awk 'match([=13=], /[a-z]+ [a-z]+/) {print substr([=13=], RSTART, RLENGTH)}'
bob mary
我希望通过简单的 bash 管道命令看到的重叠结果是:
bob mary
mary mike
mike bill
bill kim
kim jim
jim john
有什么想法吗?
Lookahead 是你的朋友
echo "bob mary mike bill kim jim john" |
perl -wnE'say " " while /(\w+)\s+(?=(\w+))/g'
重点是前瞻作为“零宽度断言”不会消耗任何东西——但它仍然允许我们捕获其中的模式。
因此,当正则表达式引擎匹配一个词和 spaces ((\w+)\s+
) 时,吞噬它们,然后停在那里并“向前看”,只是为了“断言”所寻求的模式在那里;它不会从最后一个 space 和下一个 \w
之间的位置移动,不会像他们说的那样“消耗”下一个单词。
很高兴我们还可以捕获“看到”的模式,即使它没有被消耗!所以我们得到了我们的 </code> 和 <code>
,两个词。
然后,由于 /g
修饰符,引擎继续寻找另一个词+spaces,后面还有另一个词。下一个词是我们前瞻性发现的词——所以现在一个词被消耗了,但下一个词是“寻找”(并捕获)的。等等
您也可以使用awk
awk '{for(i=1;i<NF;i++) print $i,$(i+1)}' <<< 'bob mary mike bill kim jim john'
见online demo。此解决方案遍历所有以白色 space 分隔的字段并打印当前字段 ($i
) + 字段分隔符(此处为 space)+ 后续字段值 ($(i+1)
)。
或者,另一个 perl
解决方案,它使用 very common technique 捕获正前瞻内的重叠模式:
perl -lane 'while (/(?=\b(\p{L}+\s+\p{L}+))/g) {print }' <<< 'bob mary mike bill kim jim john'
见online demo。 详情:
(?=
- 正面前瞻的开始\b
- 单词边界(\p{L}+\s+\p{L}+)
- 捕获组 1:一个或多个字母,一个或多个白色space,一个或多个字母
)
- 前瞻结束。
此处仅打印第 1 组值 ({print }
)。
性能考虑
至于这里的 Perl 解决方案,我的是最慢的,而 Timur 是最快的,但是,awk
解决方案比任何 Perl 解决方案都快。结果:
# ./wiktor_awk.sh
real 0m17.069s
user 0m12.264s
sys 0m5.314s
# ./timur_perl.sh
real 0m18.201s
user 0m15.612s
sys 0m6.139s
# ./zdim.sh
real 0m23.559s
user 0m19.883s
sys 0m7.359s
# ./wiktor_perl.sh
real 2m12.528s
user 1m52.857s
sys 0m20.201s
注意我为每个解决方案创建了 *.sh 文件,例如
#!/bin/bash
N=10000
time(
for i in $(seq 1 $N); do
<SOLUTION_HERE> &>/dev/null;
done)
和运行 for f in *.sh; do chmod +x "$f"; done
(借自here)。
使用下面的 Perl 单行代码,避免前瞻(它仍然是你的朋友):
对于以空格分隔的单词:
echo "bob mary mike bill kim jim john" | perl -lane 'print "$F[$_] $F[$_+1]" for 0..($#F-1);'
对于在 Perl 中定义为 \w+
的单词,由非单词字符分隔 \W+
:
echo "bob.mary,mike'bill kim jim john" | perl -F'/\W+/' -lane 'print "$F[$_] $F[$_+1]" for 0..($#F-1);'
Perl 单行代码使用这些命令行标志:
-e
: 告诉 Perl 查找内联代码,而不是在文件中。
-n
:一次循环输入一行,默认分配给 $_
。
-l
: 在执行内联代码之前去除输入行分隔符(默认情况下在 *NIX 上为 "\n"
),并在打印时附加它。
-a
: 在空格或 -F
选项中指定的正则表达式上将 $_
拆分为数组 @F
。
-F'/\W+/'
:在 \W+
(一个或多个非单词字符)上拆分为 @F
,而不是在空格上拆分。
$#F
:数组 @F
的最后一个索引,输入行被分割成该索引。
0..($#F-1)
:索引(数字)的范围,从数组的第一个(0
)到倒数第二个($#F-1
)索引 @F
.
$F[$_]
和$F[$_+1]
:数组@F
的两个连续元素,索引分别为$_
和$_+1
。
另请参见:
perldoc perlrun
: how to execute the Perl interpreter: command line switches
perldoc perlre
: Perl regular expressions (regexes)
perldoc perlre
: Perl regular expressions (regexes): Quantifiers; Character Classes and other Special Escapes; Assertions; Capture groups
perldoc perlrequick
: Perl regular expressions quick start