【问题标题】:Bash regex ungreedy matchBash正则表达式不贪婪的匹配
【发布时间】:2017-04-29 19:22:59
【问题描述】:

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

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

#!/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

结果:

New match: 
    Share1   Disk
    Share2  Disk
New match: Share1   Disk
    Share2 

预期的结果是

New match: Share1
New match: Share2

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

但肯定有另一种方式。我在想可能像[^\\s]+.. 但输出是:

New match: 
    Share1   Disk
New match: Share1

... 有什么想法吗?

【问题讨论】:

  • 一个想法是使用 [^\\s]+? 而不是 .+? 。如果找到空格,它将匹配字符直到出现空格。
  • 两者都产生与我在问题中已经提到的[^\\s]+ 相同的结果。我认为 bash 甚至不支持?,我的意思是在这种情况下.. 我的意思是+ 后面的? 通常意味着match ungreedy
  • 基于此answer POSIX regular expression(与=~ 运算符一起使用)没有非贪婪量词。
  • @ThomasAyoub:感谢您的指出。 @Forivin:[^\s]\S 相同。如果需要,使用 `\` 进行转义。
  • 你应该先用换行符分割字符串,然后用你的正则表达式迭代检查每个块并使用${BASH_REMATCH[1]}获取值。

标签: regex bash regex-greedy


【解决方案1】:

正如公认的答案已经指出的那样,这里的解决方案并不是真正使用非贪婪的正则表达式,因为 Bash 不支持符号 .*?(它是在 Perl 5 中引入的,并且可以在其正则表达式的语言中使用实现源于此,但 Bash 不是其中之一)。但对于在 Google 中找到此问题的访问者,标题中实际问题的答案是有时,以简单地使用比 .* 更有限的正则表达式来实现您正在寻找的非贪婪匹配。

例如,

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

这只是一个构建块;您必须从那里通过其他正则表达式匹配或循环来获取它。请参阅下面的非正则表达式变体,它基本上可以做到这一点。

如果您不想匹配特定字符,则使用取反字符类简单、优雅、方便且兼容回到 Ken Thompson 原始正则表达式库的黑暗开端。在 OP 的示例中,您似乎想跳过换行符和制表符,然后匹配任何不是文字空格的字符。

re=$'\n\t([^ ]+)'

但在这种情况下,更好的解决方案可能是在循环中实际使用parameter expansions

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[@]}"

【讨论】:

  • 另请参阅stackoverflow.com/questions/18514135/…,以更广泛地讨论如何解决 Bash(以及更普遍的 POSIX 样式的正则表达式)中缺少一些 PCRE 正则表达式功能的问题。
【解决方案2】:

这里有几个问题。首先,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"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多