使用match命令在仅有Bash的情况下查找字符串中的子串

46

虽然我几乎可以确定这已经被涵盖了,但我似乎找不到任何特定的内容。在我继续学习bash的过程中,我不断发现一些让我困惑的部分,不明白为什么事情会以这种方式发生。

当你编写脚本时,搜索和替换或只是匹配字符串中的子字符串很可能是你要做的第一件事情。但是,在bash中尝试坚持一种语言或工具集通常是很难的,因为你可以用多种方式解决大多数问题。我正在尽力让bash保持尽可能低级。我遇到了一个需要有人为我解释的问题。

在bash中使用match进行子字符串搜索,根据我使用的正则表达式,会给出不同的结果,我不知道为什么。

#!/bin/bash
Stext="Hallo World"
echo `expr "$Stext" : '^\(.[a-z]*\)'` # Hallo
echo `expr "$Stext" : '.*World'`      # 11

虽然两者都搜索单词 "I think",但是它们都没有返回它们找到的东西。为什么?


expr根本不是bash的功能--它是一个外部工具,不是shell的一部分。因此,在不同平台上安装在给定版本的bash上时,其行为不能保证始终一致,除了POSIX sh标准提供的最小保证(这些保证不承诺任何正则表达式语法超出BRE)。此外,作为外部工具意味着它执行起来要慢得多,需要一个fork()来启动子shell和一个exec()来用外部可执行文件替换该shell。 - Charles Duffy
除了 expr 是一个外部工具之外,你还在回显调用子 shell 中命令的结果,这使得效率成倍下降。应该取消这些调用,例如 expr "$Stext" : '^\(.[a-z]*\)'。(有关详细解释,请参见 https://superuser.com/questions/1352850/what-is-wrong-with-echo-stuff-or-echo-stuff) - JakeRobb
3个回答

76

您可以在 bash 中使用 BASH_REMATCH 变量来获取匹配的字符串:

$ Stext="Hallo World"
$ [[ $Stext =~ ^.[a-z]* ]] && echo $BASH_REMATCH
Hallo
$ [[ $Stext =~ ^(.[a-z]*) ]] && echo ${BASH_REMATCH[1]}
Hallo
正则表达式中用括号包含的子表达式匹配到的字符串会被保存在数组变量BASH_REMATCH中。BASH_REMATCH数组下标为0的元素代表整个正则表达式匹配到的部分。BASH_REMATCH数组下标为n的元素代表第n个子表达式匹配到的部分字符串。

33

这两个表达式是等价的,不同之处在于您使用的正则表达式:

$ echo `expr "$Stext" : '^\(.[a-z]*\)'`
Hallo
$ echo `expr "$Stext" : '^.[a-z]*'`
5
$ echo `expr "$Stext" : '\(.*World\)'`
Hallo World
$ echo `expr "$Stext" : '.*World'`
11

正如你所看到的,括号是决定返回匹配长度还是整个匹配结果的关键。

你可以在高级Bash脚本指南的第10章中找到更多示例


感谢 @jcollado 的简单解释 :) 我一直在使用您提供的文档,但不知何故没有得到这个括号功能。Bash-Scripting指南并不太容易理解。 - Adesso
2
由于这个问题涉及到 [tag:bash],建议使用 内置 正则表达式,如 @kev 建议,而不是 *fork 到 /usr/bin/expr*! - F. Hauri - Give Up GitHub

0

对于快速字符串搜索... 一种选择是使用grep。
如果未找到,则返回空值,否则就是匹配成功:

found=`echo $big | grep -e $short`

if [ ! -z $found ]; then echo 'There is a match'; else echo 'No no'; fi

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