Bash 正则表达式非贪婪匹配
Bash regex ungreedy match
我有一个正则表达式模式,它应该匹配字符串中的多个位置。我想将所有匹配组放入一个数组中,然后打印每个元素。
所以,我一直在尝试这个:
#!/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"
正如接受的答案已经指出的那样,这里的解决方案并不是真正使用 non-greedy 正则表达式,因为 Bash 不支持符号 .*?
(它在Perl 5,并且在其正则表达式实现派生自该语言的语言中可用,但 Bash 不是其中之一)。但是对于在 Google 中发现此问题的访问者,标题中实际问题的答案是 有时 只需使用比 .*
更有限的正则表达式来实现non-greedy 匹配您正在寻找的。
例如,
re='(Disk.*)'
if [[ $f =~ $re ]]; then
... # ${BASH_REMATCH[0]} contains everything after (the first occurrence of) Disk
这只是一个积木;您将不得不从那里使用其他正则表达式匹配或循环来获取它。请参阅下面的 non-regex 变体,它大体上做到了这一点。
如果您不想匹配的是特定字符,使用否定字符 class 简单、优雅、方便,并且兼容 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[@]}"
我有一个正则表达式模式,它应该匹配字符串中的多个位置。我想将所有匹配组放入一个数组中,然后打印每个元素。
所以,我一直在尝试这个:
#!/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"
正如接受的答案已经指出的那样,这里的解决方案并不是真正使用 non-greedy 正则表达式,因为 Bash 不支持符号 .*?
(它在Perl 5,并且在其正则表达式实现派生自该语言的语言中可用,但 Bash 不是其中之一)。但是对于在 Google 中发现此问题的访问者,标题中实际问题的答案是 有时 只需使用比 .*
更有限的正则表达式来实现non-greedy 匹配您正在寻找的。
例如,
re='(Disk.*)'
if [[ $f =~ $re ]]; then
... # ${BASH_REMATCH[0]} contains everything after (the first occurrence of) Disk
这只是一个积木;您将不得不从那里使用其他正则表达式匹配或循环来获取它。请参阅下面的 non-regex 变体,它大体上做到了这一点。
如果您不想匹配的是特定字符,使用否定字符 class 简单、优雅、方便,并且兼容 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[@]}"