Bash [[ test =~ regex ]] 与 perl 命令结果

Bash [[ test =~ regex ]] 与 perl 命令结果

Bash 运算符=~等同于perl调用吗?

filename="test-33.csv"
regex="([^.]+)(-\d{1,5})(\.csv)"

使用 bash 测试:

if [[ "$filename" =~ $regex ]]; then echo "it matches"; else echo "doesn't match"; fi
# doesn't match

if [[ "$filename" =~ ([^.]+)(-\d{1,5})(\.csv) ]]; then echo "matches"; else echo "doesn't match"; fi
# doesn't match

perl

result="$(perl -e "if ('$filename' =~ /$regex/) { exit 0;} else { exit 1;} ")"
if [[ result ]]; then echo "it matches"; else echo "doesn't match"; fi
# it matches

我遗漏了 bash=~运算符的什么吗?这与贪婪与非贪婪迭代器 () 有关吗[^.]+

答案1

正则表达式有几种不同的类型,每种类型都添加了更多的运算符(因此,如果要将更多的字符视为文字,则需要对它们进行转义)。

=~操作符在文档中描述man bash如下(请参阅您的系统或在线文档),

还有一个二元运算符=~,其优先级与==和相同!=。使用时,运算符右侧的字符串被视为POSIX 扩展正则表达式并进行相应匹配

grep -E扩展正则表达式 (ERE) 可以与(以前称为)匹配egrep。您的示例是 Perl 兼容正则表达式 (PCRE),它是 ERE 的超集,不能与 一起使用。但是,可以通过将其替换为来=~轻松适应:\d[[:digit:]]

echo abc-123.csv | grep -E '([^.]+)(-\d{1,5})(\.csv)'             # ERE fails
echo abc-123.csv | grep -P '([^.]+)(-\d{1,5})(\.csv)'             # PCRE matches with GNU grep

echo abc-123.csv | grep -E '([^.]+)(-[[:digit:]]{1,5})(\.csv)'    # ERE matches modified expression

因此,鉴于grep -E相当于=~我们可以这样写,

if [[ "$filename" =~ ([^.]+)(-[[:digit:]]{1,5})(\.csv) ]]
then
    echo "matches"
else
    echo "doesn't match"
fi

请注意,您的 ERE 可能应该以 为前缀^,以 为后缀$,并且[^.]+调整为[^-.]+以确保您无法匹配如下字符串abc-def-12345678-123.csv.txt

^[^-.]+-[[:digit:]]{1,5}\.csv$

如果您坚决使用 PCRE 而不是 ERE,则必须使用外部工具(例如的 GNU 实现)来grep执行匹配。但这样做效率较低,并且此处适用与上述相同的关于边界的建议:

if echo "$filename" | grep -qP '([^.]+)(-\d{1,5})(\.csv)'
then
    echo "matches"
else
    echo "doesn't match"
fi

基本 RE(RE 或 BRE)和 ERE 的 POSIX 参考位于https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html,Perl REs (PCRE) 的参考资料位于https://www.pcre.org/original/doc/html/pcrepattern.html。请注意,这两份文档都不是容易理解的。

最后,你问,

这与贪婪和非贪婪迭代器有关吗([^.]+)

这不是一个贪婪/非贪婪的迭代器。[^.]+是贪婪的,意味着“一个或多个除点 ( .)之外的任意字符“。ERE 没有非贪婪运算符。PCRE 可以定义一个非贪婪运算符,例如*或 ,+方法是在其后加上?。例如对比a*a*?;第一个将匹配a尽可能多的字符,而第二个将匹配尽可能少的字符。

括号( … )是一种分组,而不是贪婪指示器。

答案2

=~中的运算符Bash shell相当于grep -EGNU 命令。Perl正则表达式无法被它识别。您需要执行以下操作:

~$ [ $(echo "$filename" | grep -Po "$regex") ] && echo "it matches" || echo "does not match"
it matches

具有等价物。

关于grep使用的选项:

-o, --only-matching       show only the part of a line matching PATTERN
-P, --perl-regexp         PATTERN is a Perl regular expression

您的原始形式如下所示:

if [[ $(echo "$filename" | grep -Po "$regex") ]]; then echo "it matches"; else echo "does not match"; fi

这也有效:

if [ $(echo "$filename" | grep -Po "$regex") ]; then echo "it matches"; else echo "does not match"; fi

您还可以执行以下操作:

yyy@xxx:~$ filename="test-33.csv"
yyy@xxx:~$ regex="([^.]+)(-\d{1,5})(\.csv)"
yyy@xxx:~$ result=$(echo "$filename" | grep -Po "$regex")
yyy@xxx:~$ if [[ $result ]]; then echo "it matches"; else echo "does not match"; fi
it matches
yyy@xxx:~$

答案3

Bash 有扩展的 glob 模式它更接近正则表达式。运算符内部[[...]]进行==glob 样式的模式匹配。

filename=test-33.csv
# one or more non-dots, a hyphen, a digit, optionally 4 more digits, the extension
pattern='+([^.])-[0-9]?([0-9])?([0-9])?([0-9])?([0-9]).csv'
[[ $filename == $pattern ]] && echo Y || echo N

如果您使用正则表达式来过滤文件名列表,请改用 for 循环中的 glob 模式。

shopt -s extglob
for file in $pattern; do
    # do something with the file.
    echo "$file"
done

笔记

  • 命令shopt:extended glob 在其中自动启用[[...]],但在其他情况下则不启用。
  • $pattern在这些代码片段中特意不加引号,以便它被处理为一种模式而不是文字字符串。

相关内容