如何使用 BASH_REMATCH 捕获重复模式的最长匹配
How to capture the longest match of a repeating pattern using BASH_REMATCH
我正在尝试捕获重复模式的最长匹配项
do_run() {
local regex='.*((abc)+).*'
local str='_abcabcabc123_'
echo "regex=${regex}"$'\n'
echo "str=${str}"$'\n'
if [[ "${str}" =~ ${regex} ]]
then
for i in ${!BASH_REMATCH[@]}
do
echo "$i=${BASH_REMATCH[i]}"
done
else
echo "no match"
fi
}
我得到以下输出:
regex=.*((abc)+).*
str=_abcabcabc_
0=_abcabcabc123_
1=abc
2=abc
我想得到类似的东西:
regex=.*((abc)+).*
str=_abcabcabc123_
0=_abcabcabc123_
x=abcabcabc
(更新:x
只是在这里表示匹配组的索引无关紧要,但我需要知道使用什么数字来检索匹配组...)
更新:
,以下正则表达式将起作用:((abc)+)
但是,我还需要捕捉前后的内容 ((abc)+)
。
我之前没有提到它,因为我认为会应用相同的解决方案。
因此新代码将是:
do_run() {
local regex='(.*)((abc)+)(.*)'
local str='_abcabcabc123_'
echo "regex=${regex}"$'\n'
echo "str=${str}"$'\n'
if [[ "${str}" =~ ${regex} ]]
then
for i in ${!BASH_REMATCH[@]}
do
echo "$i=${BASH_REMATCH[i]}"
done
else
echo "no match"
fi
}
然后我得到以下输出:
regex=(.*)((abc)+)(.*)
str=_abcabcabc123_
0=_abcabcabc123_
1=_abcabc
2=abc
3=abc
4=123_
我希望能够从匹配组中检索 abcabcabc
以及它之前和之后的内容
I also need to capture what precedes and what follows ((abc)+).
为此,通常您需要使用 perl 正则表达式进行否定前瞻,(?<!abc)((abs)+)(.*)
。
我不擅长 perl 正则表达式,启用了 perl grep
我能够做到这一点:
$ grep -oxP '(.*)(?<!abc)((abc)+)\K(.*)' <<<'_abcabcabc123_'
123_
$ grep -oP '((abc)+)' <<<'_abcabcabc123_'
abcabcabc
$ rev <<<'_abcabcabc123_' | grep -oP '(.*)(?<!cba)((cba)+)\K(.*)' | rev
_
Bash 没有环顾四周,也没有 perl 正则表达式。考虑使用 python 或 perl。
但是您可以通过拆分正则表达式上的部分然后读取行来使用 sed,这可能更简单:
$ readarray -t lines < <(<<<'_abcabcabc123_' sed -E 's/((abc)+)/\n&\n/'); declare -p lines
declare -a lines=([0]="_" [1]="abcabcabc" [2]="123_")
另一个想法:您可以使用 bash 扩展来用独特的东西替换 abc
部分,然后在该分隔符上拆分它:
$ IFS=' ' read -r before post < <(printf "%s\n" "${str//abc/ }") ; declare -p before post
declare -- before="_"
declare -- post="123_"
# or
$ IFS='@' read -r before post < <(<<<"${str//abc/@}" tr -s '@') ; declare -p before post
declare -- before="_"
declare -- post="123_"
作为解决方法,您可以这样做:
[STEP 101] $ cat foo.sh
v=_abcabcabc123_
if [[ $v =~ (abc)+ ]]; then
middle=${BASH_REMATCH[0]}
[[ $v =~ (.*)"$middle" ]]
before=${BASH_REMATCH[1]}
[[ $v =~ "$middle"(.*) ]]
after=${BASH_REMATCH[1]}
echo "before: $before"
echo "middle: $middle"
echo "after : $after"
fi
[STEP 102] $ bash foo.sh
before: _
middle: abcabcabc
after : 123_
[STEP 103] $
对于您给定的输入,此正则表达式可以工作:
re='^([^a]|a[^b]*|ab[^c]*)((abc)+)(.*)'
str='_abcabcabc123_'
[[ $str =~ $re ]] && declare -p BASH_REMATCH
输出:
declare -ar BASH_REMATCH=([0]="_abcabcabc123_" [1]="_" [2]="abcabcabc" [3]="abc" [4]="123_")
所以你可以使用:
"${BASH_REMATCH[1]}" # string before
"${BASH_REMATCH[2]}" # string containing all "abc"s
"${BASH_REMATCH[4]}" # string after
我正在尝试捕获重复模式的最长匹配项
do_run() {
local regex='.*((abc)+).*'
local str='_abcabcabc123_'
echo "regex=${regex}"$'\n'
echo "str=${str}"$'\n'
if [[ "${str}" =~ ${regex} ]]
then
for i in ${!BASH_REMATCH[@]}
do
echo "$i=${BASH_REMATCH[i]}"
done
else
echo "no match"
fi
}
我得到以下输出:
regex=.*((abc)+).*
str=_abcabcabc_
0=_abcabcabc123_
1=abc
2=abc
我想得到类似的东西:
regex=.*((abc)+).*
str=_abcabcabc123_
0=_abcabcabc123_
x=abcabcabc
(更新:x
只是在这里表示匹配组的索引无关紧要,但我需要知道使用什么数字来检索匹配组...)
更新:
((abc)+)
但是,我还需要捕捉前后的内容 ((abc)+)
。
我之前没有提到它,因为我认为会应用相同的解决方案。
因此新代码将是:
do_run() {
local regex='(.*)((abc)+)(.*)'
local str='_abcabcabc123_'
echo "regex=${regex}"$'\n'
echo "str=${str}"$'\n'
if [[ "${str}" =~ ${regex} ]]
then
for i in ${!BASH_REMATCH[@]}
do
echo "$i=${BASH_REMATCH[i]}"
done
else
echo "no match"
fi
}
然后我得到以下输出:
regex=(.*)((abc)+)(.*)
str=_abcabcabc123_
0=_abcabcabc123_
1=_abcabc
2=abc
3=abc
4=123_
我希望能够从匹配组中检索 abcabcabc
以及它之前和之后的内容
I also need to capture what precedes and what follows ((abc)+).
为此,通常您需要使用 perl 正则表达式进行否定前瞻,(?<!abc)((abs)+)(.*)
。
我不擅长 perl 正则表达式,启用了 perl grep
我能够做到这一点:
$ grep -oxP '(.*)(?<!abc)((abc)+)\K(.*)' <<<'_abcabcabc123_'
123_
$ grep -oP '((abc)+)' <<<'_abcabcabc123_'
abcabcabc
$ rev <<<'_abcabcabc123_' | grep -oP '(.*)(?<!cba)((cba)+)\K(.*)' | rev
_
Bash 没有环顾四周,也没有 perl 正则表达式。考虑使用 python 或 perl。
但是您可以通过拆分正则表达式上的部分然后读取行来使用 sed,这可能更简单:
$ readarray -t lines < <(<<<'_abcabcabc123_' sed -E 's/((abc)+)/\n&\n/'); declare -p lines
declare -a lines=([0]="_" [1]="abcabcabc" [2]="123_")
另一个想法:您可以使用 bash 扩展来用独特的东西替换 abc
部分,然后在该分隔符上拆分它:
$ IFS=' ' read -r before post < <(printf "%s\n" "${str//abc/ }") ; declare -p before post
declare -- before="_"
declare -- post="123_"
# or
$ IFS='@' read -r before post < <(<<<"${str//abc/@}" tr -s '@') ; declare -p before post
declare -- before="_"
declare -- post="123_"
作为解决方法,您可以这样做:
[STEP 101] $ cat foo.sh
v=_abcabcabc123_
if [[ $v =~ (abc)+ ]]; then
middle=${BASH_REMATCH[0]}
[[ $v =~ (.*)"$middle" ]]
before=${BASH_REMATCH[1]}
[[ $v =~ "$middle"(.*) ]]
after=${BASH_REMATCH[1]}
echo "before: $before"
echo "middle: $middle"
echo "after : $after"
fi
[STEP 102] $ bash foo.sh
before: _
middle: abcabcabc
after : 123_
[STEP 103] $
对于您给定的输入,此正则表达式可以工作:
re='^([^a]|a[^b]*|ab[^c]*)((abc)+)(.*)'
str='_abcabcabc123_'
[[ $str =~ $re ]] && declare -p BASH_REMATCH
输出:
declare -ar BASH_REMATCH=([0]="_abcabcabc123_" [1]="_" [2]="abcabcabc" [3]="abc" [4]="123_")
所以你可以使用:
"${BASH_REMATCH[1]}" # string before
"${BASH_REMATCH[2]}" # string containing all "abc"s
"${BASH_REMATCH[4]}" # string after