从Grep RegEx捕获组


380

我在sh(Mac OSX 10.6)中有了这个小脚本,可以查看文件数组。Google现已停止提供帮助:

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

到目前为止(显然,对于您的shell专家来说)$name仅保留0、1或2,具体取决于是否grep发现文件名与提供的文件匹配。我想要的是捕获内部的内容([a-z]+)并将其存储到变量中

如果可能的话,我只想使用grep。如果没有,请不要使用Python或Perl等sed类似的东西–我是Shell的新手,并且希望从* nix纯粹主义者的角度进行攻击。

另外,作为超酷的bonu,我很好奇如何在shell中连接字符串?我捕获的组是$ name中存储的字符串“ somename”,我想在其末尾添加字符串“ .jpg” cat $name '.jpg'吗?

如果有时间,请解释发生了什么。


30
grep是否真的比sed更纯净的unix?
马丁克莱顿

3
啊,不是要暗示这个。我只是希望可以使用我在这里专门尝试学习的工具找到解决方案。如果无法解决using grep,那么sed可能会很棒,如果可以解决using sed
艾萨克(Isaac)

2
我应该在那首歌上放一个:) ...
马丁·克莱顿

Psh,我的大脑今天太炸了哈哈。
艾萨克

2
@martinclayton那将是一个有趣的论点。我确实认为sed(或更确切地说是ed)会更​​老(因此更纯净?也许是?),因为grep是从ed表达式g(lobal)/ re(gular expression)/ p(rint)派生出来的。
2013年

Answers:


499

如果您使用的是Bash,则甚至不必使用grep

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files    # unquoted in order to allow the glob to expand
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

最好将正则表达式放在变量中。如果按字面意义包含某些模式,将无法使用。

这使用的 =~是Bash的正则表达式匹配运算符。匹配结果将保存到名为的数组中$BASH_REMATCH。第一个捕获组存储在索引1中,第二个(如果有)存储在索引2中,依此类推。索引零是完全匹配项。

您应该意识到,没有锚,此正则表达式(以及使用的正则表达式grep)将匹配以下任何示例以及更多示例,而这些可能并不是您想要的:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

为了消除第二个和第四个示例,使正则表达式如下所示:

^[0-9]+_([a-z]+)_[0-9a-z]*

表示字符串必须以一个或多个数字开头。克拉代表弦的开头。如果在正则表达式的末尾添加美元符号,如下所示:

^[0-9]+_([a-z]+)_[0-9a-z]*$

那么第三个示例也将被删除,因为该点不在正则表达式中,并且美元符号表示字符串的结尾。请注意,第四个示例也未能通过此匹配。

如果您使用的是GNU grep(大约2.5或更高版本,我想\K是在添加运算符时):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

\K操作者(可变长度向后看)导致前述图案匹配,但不包括在结果中的匹配。固定长度等效项是(?<=)-模式将包含在右括号之前。\K如果量词可以匹配不同长度的字符串(例如,,)+,则必须使用。*{2,4}

所述(?=)操作者匹配固定的或可变长度的模式和被称为“先行”。它也不在结果中包含匹配的字符串。

为了使匹配不区分大小写,使用了(?i)运算符。它会影响其后的模式,因此其位置很重要。

根据文件名中是否还有其他字符,可能需要调整正则表达式。您会注意到,在这种情况下,我展示了一个在捕获子字符串的同时串联一个字符串的示例。


48
在这个答案中,我想表达特定的说法:“最好将正则表达式放在变量中。如果确实包含某些模式,则某些模式将不起作用。”
Brandin 2014年

5
@FrancescoFrassinelli:一个示例是包含空格的模式。转义很尴尬,您不能使用引号,因为这会将其从正则表达式强制转换为普通字符串。正确的方法是使用变量。作业期间可以使用引号,使事情变得更加简单。
暂停,直到另行通知。

5
/K运算符。
razz

2
@布兰登:确实有效。您正在使用什么版本的Bash?告诉我你在做什么,那是行不通的,也许我可以告诉你原因。
暂停,直到另行通知。

2
@mdelolmo:我的回答包括有关的信息grep。OP也接受了它,并对此进行了很多批评。谢谢你的反对。
暂停,直到另行通知。

145

对于pure grep,这实际上是不可能的,至少通常不是这样。

但是,如果您的模式合适,则可以grep在管道中使用多次,以首先将行缩小为已知格式,然后仅提取所需的位。(尽管工具类似,cut并且sed在此方面要好得多)。

为了争辩,假设您的模式稍微简单一些:[0-9]+_([a-z]+)_您可以这样提取:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

第一个grep将删除与整体样式不匹配的所有行,第二个grep(已--only-matching指定)将显示名称的alpha部分。这仅适用于该模式,因为该模式合适:“ alpha部分”足够具体以提取您想要的内容。

(此外:就我个人而言,我将使用grep+ cut来实现您所追求的:echo $name | grep {pattern} | cut -d _ -f 2。这可以cut通过在定界符上进行拆分将行解析为字段_,并仅返回字段2(字段编号从1开始))。

Unix的哲学是要有一种工具,它可以做一件事情,并做得很好,并将它们组合起来以完成不平凡的任务,因此我认为grep+ sedetc是一种更Unixy的工作方式:-)


3
for f in $files; do name=回声$ f | grep -oEi'[0-9] + _([az] +)_ [0-9a-z] *'|| 切-d _ -f 2 ;阿哈!
艾萨克

2
我不同意这种“哲学”。如果您可以在不调用外部命令的情况下使用Shell的内置功能,那么脚本的性能将大大提高。有一些功能重叠的工具。例如grep和sed和awk。它们都可以进行字符串操作,但是awk在它们之上脱颖而出,因为它可以做更多的事情。实际上,可以通过一个awk流程来缩短所有这些命令链,例如上述两次打钩或grep + sed。
ghostdog74

7
@ ghostdog74:这里没有理由说将许多微小的操作链接在一起通常比在一个地方进行所有操作效率低,但是我坚持认为Unix理念是许多工具可以一起工作。例如,tar只是存档文件,它不会压缩文件,因为默认情况下它会输出到STDOUT,所以您可以使用netcat通过网络将其传输到管道,或者使用bzip2进行压缩,以此类推。 Unix工具应该能够在管道中一起工作的想法。
RobM,2009年

剪裁很棒-感谢小费!至于工具与效率的争论,我喜欢链接工具的简单性。
ether_joe 2014年

grep的o选项的道具非常有用
chiliNUT

96

我意识到已经为此接受了一个答案,但是从“严格的* nix纯粹主义者角度”看来,似乎合适的工具是pcregrep,但似乎尚未提及。尝试更改行:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

到以下内容:

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

仅获取捕获组的内容1。

pcregrep工具利用了您已经使用过的所有相同语法grep,但是实现了所需的功能。

如果该参数是裸露的,则其功能-ogrep版本相同,但是它也接受中的数字参数pcregrep,该数字参数指示要显示的捕获组。

使用此解决方案,脚本中几乎不需要进行任何更改。您只需将一个模块化实用程序替换为另一个,即可调整参数。

有趣的注意:您可以使用多个-o参数按它们在行中出现的顺序返回多个捕获组。


3
pcregrep默认情况下不可用,Mac OS X这是OP使用的功能
grebneke 2014年

4
我的pcregrep-opcregrep --help
名字

1
@WAF抱歉,我应该在我的评论中包含该信息。我在CentOS 6.5和pcregrep版本显然是很老:7.8 2008-09-05
彼得·赫登堡

2
是的,非常有帮助,例如echo 'r123456 foo 2016-03-17' | pcregrep -o1 'r([0-9]+)' 123456
zhuguowei

5
pcregrep8.41(安装在apt-get install pcregrepUbuntu 16.03)无法识别该-Ei开关。但是,如果没有它,它会完美地工作。在macOS pcregrephomebrew(如上@anishpatel所述,也是通过(也是8.41)安装的),至少在High Sierra上,该-E开关也无法识别。
Ville

27

我相信仅仅grep是不可能的

对于sed:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

我会为红利而刺痛:

echo "$name.jpg"

2
不幸的是,该sed解决方案不起作用。它只是打印出我目录中的所有内容。
艾萨克(

更新后,如果没有匹配项,将输出空白行,因此请务必进行检查
-cobbal

现在,它仅输出空白行!
艾萨克

这个sed有问题。第一组捕获括号的内容包括所有内容。当然\ 2将一无所有。
ghostdog74

它适用于一些简单的测试用例... \ 2获得内部组
cobbal

16

这是使用gawk的解决方案。我发现我需要经常使用它,所以我为此创建了一个函数

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

用就做

$ echo 'hello world' | regex1 'hello\s(.*)'
world

很棒的主意,但似乎不适用于正则表达式中的空格-需要将其替换为\s。你知道怎么解决吗?
亚当·里奇科夫斯基

4

给您的建议-您可以使用参数扩展从最后一个下划线开始删除名称的一部分,并且类似地在开始时:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

然后name就会有价值abc

请参阅Apple 开发人员文档,向前搜索“参数扩展”。


这不会检查([az] +)。
ghostdog74

@levislevis-是的,但是,正如OP所评论的那样,它确实可以完成所需的工作。
马丁克莱顿

2

如果您有bash,则可以使用扩展的globing

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

要么

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

看起来很有趣。您能否在此附上一点解释?或者,如果您愿意,可以链接到一个特别有见地的资源来解释它?谢谢!
以撒2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.