Bash正则表达式--似乎无法匹配任何\s \S \d \D \w \W等字符

21

我有一个脚本,试图从gparted获取信息块。

我的数据看起来像:

Disk /dev/sda: 42.9GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos

Number  Start   End     Size    Type     File system     Flags
 1      1049kB  316MB   315MB   primary  ext4            boot
 2      316MB   38.7GB  38.4GB  primary  ext4
 3      38.7GB  42.9GB  4228MB  primary  linux-swap(v1)

log4net.xml
Model: VMware Virtual disk (scsi)
Disk /dev/sdb: 42.9GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos

Number  Start   End     Size    Type     File system     Flags
 1      1049kB  316MB   315MB   primary  ext4            boot
 5      316MB   38.7GB  38.4GB  primary  ext4
 6      38.7GB  42.9GB  4228MB  primary  linux-swap(v1)

我使用正则表达式将其分成两个磁盘块。

^Disk (/dev[\S]+):((?!Disk)[\s\S])*

使用多行功能,此方法有效。

当我在bash脚本中测试时,似乎无法匹配\s\S,我做错了什么?

我是通过以下脚本进行测试的:

data=`cat disks.txt`
morematches=1
x=0
regex="^Disk (/dev[\S]+):((?!Disk)[\s\S])*"

if [[ $data =~ $regex ]]; then
echo "Matched"
while [ $morematches == 1 ]
do
        x=$[x+1]
        if [[ ${BASH_REMATCH[x]} != "" ]]; then
                echo $x "matched" ${BASH_REMATCH[x]}
        else
                echo $x "Did not match"
                morematches=0;
        fi

done

fi

但是,当我测试正则表达式的部分时,每当我匹配到\s\S时,它都不能正常工作——我做错了什么?


\s\S是PCRE扩展,它们不在ERE(Posix扩展正则表达式)标准中。只要庆幸你没有尝试使用BRE。 - Charles Duffy
1
顺便提一下,许多PCRE扩展都是经过不太理智的思考而设计的,最坏情况下的性能非常可怕(特别是前瞻/后顾)。通常来说,选择使用ERE是非常有道理的。 - Charles Duffy
1
请特别参考http://swtch.com/~rsc/regexp/regexp1.html。 - Charles Duffy
2
在一些其他的观点上,x=$[x+1] 是一个古老的语法;((x++)) 是现代的 bash 版本,或者 x=$((x + 1)) 是现代的 POSIX 版本。在 [ ] 中使用 == 不符合 POSIX 标准;要么使用 [[ ]](它不试图符合 POSIX 标准,并且通过具有关闭字符串分割的解析时规则来允许您不引用),要么使用 = 而不是 ==(并将其设置为 [ "$morematches" = 1 ],带有引号!)。始终引用您的扩展:echo "$x did not match";否则,在 $x 中的通配符会被扩展并压缩空格的运行。 - Charles Duffy
Duffy:好知道!我通常不会在bash shell脚本中做太多事情。 - Yablargo
显示剩余3条评论
6个回答

28

也许\S和\s不被支持,或者你不能将它们放在[ ]周围。尝试使用以下正则表达式替代:

也许不支持\S和\s,或者不能将它们放在[ ]中间。请改用以下正则表达式:

^Disk[[:space:]]+/dev[^[:space:]]+:[[:space:]]+[^[:space:]]+

编辑

看起来你实际上想要获取匹配的字段。为此,我简化了脚本。

#!/bin/bash 

regex='^Disk[[:space:]]+(/dev[^[:space:]]+):[[:space:]]+(.*)'

while read line; do
    [[ $line =~ $regex ]] && echo "${BASH_REMATCH[1]} matches ${BASH_REMATCH[2]}."
done < disks.txt

产生:

/dev/sda matches 42.9GB.
/dev/sdb matches 42.9GB.

1
[[:alnum:]][[:digit:]] 可能比 "^space" 结构更好(即使这些结构与 OP 所要求的匹配)。 - Mat
@Mat 是的,这也可能是一个选项 :) - konsolebox

17

因为这是一个常见的FAQ,让我列举一些在Bash(和相关工具如sedgrep等)中不支持的结构,以及如何解决它们,如果存在简单的解决方法。

常见使用的正则表达式有多种方言。Bash支持的是扩展正则表达式的变体。这与许多在线正则表达式测试器支持的不同,后者通常是更现代的Perl 5 / PCRE变量。

  • Bash不支持 \d \D \s \S \w \W -- 这些可以用POSIX字符类等效项来替换,分别为 [[:digit:]], [^[:digit:]], [[:space:]], [^[:space:]], [_[:alnum:]], 和 [^_[:alnum:]]。(请注意,最后一种情况下,[:alnum:] POSIX字符类添加了下划线,以完全等效于Perl的 \w 简写。)
  • Bash不支持非贪婪匹配。您有时可以将a.*?b替换为类似a[^ab]*b的表达式,以在实践中获得类似的效果,尽管这两者并不完全等效。
  • Bash不支持非捕获括号(?:...)。在简单情况下,只需使用捕获括号(...)即可;但是,如果您使用捕获组和/或反向引用,则这将重新编号您的捕获组。
  • Bash不支持像 (?<=before)(?!after) 这样的先行断言,事实上任何带有(?的都是Perl扩展。对于这些问题,没有简单的通用解决方法,但您通常可以重新构造您的问题,以避免使用先行断言。

https://stackoverflow.com/questions/19453991/is-it-possible-to-perform-look-behind-and-look-ahead-assertions-in-grep-without 上有一些重新实现环视的想法。 - tripleee
或许可以看一下 为什么有这么多不同的正则表达式方言? - tripleee
Bash在某些情况下确实支持\s和其他字符,详见下面的回答。 - alecov

4

来自man bash

另外还有一个二元运算符=~,优先级与==!=相同。当它被使用时,操作符右侧的字符串被视为扩展正则表达式并相应地匹配(就像在regex(3)中一样)。

ERE不支持向前/向后查看,但是您在代码中使用了它们((?!Disk))。

这就是为什么您的正则表达式无法按预期进行匹配的原因。


那还有\s\S的缺乏。 - Adrian Frühwirth
@AdrianFrühwirth \s\S应该没问题,可以看看我的回答,我添加了那一部分。 - Kent
2
\s\S在实践中可能有效,但是Bash文档并不保证它们会起作用——只有由regex(3)解析的ERE语法被保证支持,并且POSIX ERE标准不包括这些快捷方式。因此,依赖它们是不幸和脆弱的。 - Charles Duffy
查尔斯是对的...在我的系统上我得到:[[ "aaa" =~ "\S+" ]] && echo "yes" || echo "no" --> no - dcsohl
1
@CharlesDuffy 对不起,我是在 zsh 中测试它......你是对的,我会移除 \s \S 部分。 - Kent

2

Bash支持您系统上regcomp(3)支持的内容。Glibc的实现确实支持\s和其他内容,但由于Bash在二进制运算符上引用东西的方式,您无论如何都不能直接编码正确的\s:

[[ 'a   b' =~ a[[:space:]]+b ]] && echo ok # OK
[[ 'a   b' =~ a\s+b ]] || echo fail        # Fail
[[ 'a   b' =~ a\\s+b ]] || echo fail       # Fail
[[ 'a   b' =~ a\\\s+b ]] || echo fail      # Fail

使用模式变量处理这个问题会更加简单:
pattern='a\s+b'
[[ 'a   b' =~ $pattern ]] && echo ok # OK

1
这显然只适用于使用Glibc编译Bash的系统。对我来说,在Ubuntu上它可以直接使用,但在MacOS上不行。 - tripleee

0

我知道你已经“解决”了这个问题,但是你最初的问题可能只是没有在测试中引用$regex这个变量。也就是说:

if [[ $data =~ "$regex" ]]; then

Bash变量扩展将简单地插入字符串,而您原始正则表达式中的空格将导致测试出错,原因如下:
regex="^Disk (/dev[\S]+):((?!Disk)[\s\S])*"
if [[ $data =~ $regex ]]; then

是等同于:

if [[ $data =~ ^Disk (/dev[\S]+):((?!Disk)[\s\S])* ]]; then

而bash/test将会愉快地解释一个额外的参数和所有那些未被引用的元字符。

请记住,bash不是“传递”变量,它是“扩展”它们。


在我进行了20分钟的速成课后,这让我很困惑;)最终,我只是编写了一个小的perl脚本来调用,这样就简单多了。我之前没有意识到bash正则表达式的约定是如此不同的,因为几乎所有其他我使用过的都支持perl风格。 - Yablargo
2
这个答案实际上并不正确 - [[ 有它自己的解析处理方式;如果在右侧引用,它将把内容视为字面字符串,如果未引用,则视为正则表达式;它 不会 执行单词分割或通配符。这意味着 regex='.+'; [[ $data =~ $regex ]] 匹配任何非空字符串,而 regex='.+'; [[ $data =~ "$regex" ]] 只匹配包含确切文本 .+ 的字符串。 - Charles Duffy

0
此外,[\s\S] 等同于 .,即任何字符。在我的 shell 中,[^\s] 可以工作,但是 [\S] 不能。

[^\s] doesn't do what you think, it just matches a string which isn't s - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接