如何使用 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

RegEx Demo