Bash regex ungreedy匹配

For*_*vin 7 regex bash regex-greedy

我有一个应该在字符串中的多个位置匹配的正则表达式模式.我想将所有匹配组放入一个数组中,然后打印每个元素.

所以,我一直在尝试这个:

#!/bin/bash

f=$'\n\tShare1   Disk\n\tShare2  Disk\n\tPrnt1  Printer'
regex=$'\n\t(.+?)\\s+Disk'
if [[ $f =~ $regex ]]
then
    for match in "${BASH_REMATCH[@]}"
    do
        echo "New match: $match"
    done
else
    echo "No matches"
fi
Run Code Online (Sandbox Code Playgroud)

结果:

New match: 
    Share1   Disk
    Share2  Disk
New match: Share1   Disk
    Share2 
Run Code Online (Sandbox Code Playgroud)

预期的结果将是

New match: Share1
New match: Share2
Run Code Online (Sandbox Code Playgroud)

我认为它不起作用,因为我.+?匹配贪婪.所以我查看了如何使用bash正则表达式实现这一点.但是每个人似乎都建议在perl正则表达式中使用grep.

但肯定还有另一种方式.我想的可能是[^\\s]+......但是输出的结果是:

New match: 
    Share1   Disk
New match: Share1
Run Code Online (Sandbox Code Playgroud)

... 有任何想法吗?

tri*_*eee 6

正如已接受的答案已经指出的那样,这里的解决方案并不是真正使用非贪婪的正则表达式,因为 Bash 不支持该表示法.*?(它是在 Perl 5 中引入的,并且可在其正则表达式实现派生的语言中使用,但是Bash 不是其中之一)。但对于在 Google 中找到此问题的访问者来说,标题中实际问题的答案有时只是使用更有限的正则表达式,而不是.*实现您正在寻找的非贪婪匹配。

例如,

re='(Disk.*)'
if [[ $f =~ $re ]]; then
 ... # ${BASH_REMATCH[0]} contains everything after (the first occurrence of) Disk
Run Code Online (Sandbox Code Playgroud)

这只是一个构建块;您必须从那里使用额外的正则表达式匹配或循环来获取它。请参阅下面的非正则表达式变体,它大体上可以实现此目的。

如果您不想匹配的是特定字符,那么使用否定字符类是简单、优雅、方便的,并且可以追溯到 Ken Thompson 原始正则表达式库的黑暗开端。在OP的示例中,看起来您想跳过换行符和制表符,然后匹配非文字空格的任何字符。

re=$'\n\t([^ ]+)'
Run Code Online (Sandbox Code Playgroud)

但在这种情况下,更好的解决方案可能是在循环中实际使用参数扩展。

f=$'\n\tShare1   Disk\n\tShare2  Disk\n\tPrnt1  Printer'
result=()
f=${f#$'\n\t'}      # trim any newline + tab prefix
while true; do
  case $f in
    *\ Disk*)
        d=${f%% *}           # capture up to just before first space
        result+=("$d")
        f=${f#*$'\n\t'}     # trim up to next newline + tab
        ;;
    *)
        break ;;
  esac
done
echo "${result[@]}"
Run Code Online (Sandbox Code Playgroud)


Eri*_*ouf 5

这里有几个问题.首先,第一个元素BASH_REMATCH是与模式匹配的整个字符串,而不是捕获组,因此您希望使用它${BASH_REMATCH[@]:1}来获取捕获组中的那些内容.

但是,bash正则表达式不支持在字符串中多次重复匹配,因此bash可能不适合此作业.因为事情是在他们自己的行上,你可以尝试使用它来分割东西并将模式应用于每一行,如:

f=$'\n\tShare1   Disk\n\tShare2  Disk\n\tPrnt1  Printer'
regex=$'\t(\S+?)\\s+Disk'
while IFS=$'\n' read -r line; do
    if [[ $line =~ $regex ]]
    then
        printf 'New match: %s\n' "${BASH_REMATCH[@]:1}"
    else
        echo "No matches"
    fi
done <<<"$f"
Run Code Online (Sandbox Code Playgroud)