我有一个正则表达式模式,应该在字符串中的多个位置匹配。我想将所有匹配组放入一个数组中,然后打印每个元素。
所以,我一直在尝试这个:
#!/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
... 有什么想法吗?
这里有几个问题。首先,
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"
正如已接受的答案已经指出的那样,这里的解决方案并不是真正使用非贪婪的正则表达式,因为 Bash 不支持符号
.*?
(它是在 Perl 5 中引入的,并且可以在正则表达式实现派生的语言中使用)从那开始,但 Bash 不是其中之一)。但对于在 Google 中找到此问题的访问者来说,标题中实际问题的答案是 sometimes,只需使用比 .*
更有限的正则表达式来实现您正在寻找的非贪婪匹配。
例如,
re='(Disk.*)'
if [[ $f =~ $re ]]; then
... # ${BASH_REMATCH[0]} contains everything after (the first occurrence of) Disk
这只是一个构建块;您必须从那里使用额外的正则表达式匹配或循环来获取它。请参阅下面的非正则表达式变体,它大体上可以实现此目的。
如果您不想匹配的是特定字符,那么使用否定字符类是简单、优雅、方便的,并且兼容回到 Ken Thompson 原始正则表达式库的黑暗开端。在OP的示例中,看起来您想跳过换行符和制表符,然后匹配非文字空格的任何字符。
re=$'\n\t([^ ]+)'
但在这种情况下,更好的解决方案可能是在循环中实际使用参数扩展。
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[@]}"
我遇到了一个非常相似的问题并通过以下方式解决了它。
#!/bin/bash
# Captures all %{...} patterns and stops greedy matching by not matching
# the } inside using [^}] yet capturing it once outside.
# It also matches all remaining characters.
regex="^[^}]*(%{[^}]+})(.*)"
URL="http://%{host}/%{path1}/%{path2}"
value=$URL
matches=()
while true
do
if [[ $value =~ $regex ]]
then
matches+=( ${BASH_REMATCH[1]} )
value=${BASH_REMATCH[2]};
echo "Yes: ${BASH_REMATCH[1]} ${BASH_REMATCH[2]}";
else
break;
fi
done
echo ${matches[@]}
上面的输出如下,最后一行是匹配数组:
$ . loop-match.sh
Yes: %{host} /%{path1}/%{path2}
Yes: %{path1} /%{path2}
Yes: %{path2}
%{host} %{path1} %{path2}
我正在寻找一种通用的解决方案来解决匹配/替换字符串中间的第一个和最长的实例的问题,而不依赖于否定。 否定会增加不必要的复杂性,并且由于 ERE 的限制而并不总是有效。 我希望模式
y
能够在 (x)(y)(z)
中匹配,但是 x
却无法匹配。
我发现除了正则表达式匹配之外,还可以通过使用子字符串来实现。
最简单的情况是模式的
x
部分不需要特别匹配任何内容,例如 (.*?)(baz)(.*)
。
删除表达式的 x
部分,然后从目标字符串构建隐式匹配:
text='Foo bar, baz qux. Wiz huz baz dux.'
re='(baz)(.*)'
if [[ "$text" =~ $re ]]; then
before_end=$(( ${#text} - ${#BASH_REMATCH[0]} ))
# obviously no need to put $text back into the result
# there only to demo emulation of $BASH_REMATCH for (.*?)(baz)(.*)
ungreedy_rematch=( "$text" "${text:0:before_end}" "${BASH_REMATCH[@]:1}" )
# inspect
(IFS='|'; echo "$IFS${ungreedy_rematch[*]}$IFS")
# produces: |Foo bar, baz qux. Wiz huz baz dux.|Foo bar, |baz| qux. Wiz huz baz dux.|
# replacement
text="${ungreedy_rematch[1]}boz${ungreedy_rematch[3]}"
echo "|$text|"
# produces: |Foo bar, boz qux. Wiz huz baz dux.|
fi
如果
x
部分确实需要匹配某些内容,就像提问者的情况一样,请重复这个技巧:
f=$'\n\tShare1 Disk\n\tShare2 Disk\n\tPrnt1 Printer'
the_rest="$f"
regex_before=$'\n\t(.*)'
regex_after=$'\\s+Disk(\n.*|$)' # desired match is implied before this one
while [[ "$the_rest" =~ $regex_before ]]; do
# ignore this implied match
the_rest="${BASH_REMATCH[1]}"
if [[ "$the_rest" =~ $regex_after ]]; then
# get this implied match
before_end=$(( ${#the_rest} - ${#BASH_REMATCH[0]} ))
match="${the_rest:0:before_end}"
the_rest="${BASH_REMATCH[1]}"
echo "New match: $match"
else
break
fi
done
在每种情况下,模式必须消耗整个字符串的其余部分才能计算隐含匹配的偏移量。 如果您需要更多的可重用性,请将逻辑包装在 shell 函数中。