从Grep RegEx捕获组

sh (Mac OSX 10.6)中有这个小脚本来查看文件数组。 Google在这一点上停止了帮助:

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

到目前为止(显然,对于你的shell的大师) $name只能保存0,1或2,这取决于如果grep发现文件名符合提供的问题。 我想要捕捉什么是parens ([az]+) ,并将其存储到一个variables

如果可能,我只想使用grep 。 如果没有,请不要Python或Perl等sed或类似的东西 – 我是新的壳,并希望从* nix纯化angular攻击这个。

另外,作为一个超酷的bonu s,我很好奇我如何在shell中连接string? 我捕获的是在$ name存储的string“somename”,我想添加string“.jpg”到它的末尾,我可以cat $name '.jpg'

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

如果你使用Bash,你甚至不需要使用grep

 files="*.jpg" regex="[0-9]+_([az]+)_[0-9a-z]*" for f in $files 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 

将正则expression式放在一个variables中更好。 如果包含字面,某些模式将不起作用。

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

你应该知道,如果没有锚,这个正则expression式(和使用grep的那个)将匹配以下任何一个例子和更多,这可能不是你正在寻找的:

 123_abc_d4e5 xyz123_abc_d4e5 123_abc_d4e5.xyz xyz123_abc_d4e5.xyz 

要消除第二个和第四个例子,使你的正则expression式是这样的:

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

其中说,string必须以一个或多个数字开始。 克拉代表string的开始。 如果在正则expression式的末尾添加美元符号,如下所示:

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

那么第三个例子也将被消除,因为点不在正则expression式中的字符之间,美元符号表示string的结尾。 请注意,第四个例子也没有通过这个匹配。

如果你有GNU grep (约2.5或更高版本,我认为,当\K运营商被添加时):

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

\K运算符(可变长度后顾)导致前面的模式匹配,但不包含结果中的匹配。 固定长度的等价物是(?<=) – 该模式将包括在右括号之前。 如果量词可以匹配不同长度的string(例如+*{2,4} ),则必须使用\K

(?=)运算符匹配固定或可变长度的模式,称为“预见”。 它也不包含结果中的匹配string。

为了使匹配不区分大小写,使用(?i)运算符。 它会影响后续的模式,所以它的地位非常重要。

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

纯粹的grep是不可能的,至less不是一般的。

但是如果你的模式是合适的,你可以在pipe道中多次使用grep来首先将你的行减less到一个已知的格式,然后提取你想要的位。 (虽然像cutsed这样的工具在这方面要好得多)。

假设为了争辩,你的模式有点简单: [0-9]+_([az]+)_你可以这样解压:

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

第一个grep会删除所有与你的整个--only-matching不匹配的行,第二个grep--only-matching指定的--only-matching )会显示这个名字的alpha部分。 这只适用于模式,因为这个模式是合适的:“alpha部分”足够具体,可以抽出你想要的东西。

(另外:就个人而言,我会用grep + cut来达到你的效果: echo $name | grep {pattern} | cut -d _ -f 2这样就可以将行分割成分隔符, ,并且只返回字段2(字段号从1开始))。

Unix的哲学是有工具做一件事,做得很好,并把它们结合起来以实现非平凡的任务,所以我认为grep + sed等是一个更加unix的做事方式:-)

我意识到这个答案已经被接受了,但是从“严格的”nix纯粹主义的angular度来看,似乎这个工作的正确工具是pcregrep ,这似乎还没有被提及。 尝试改变线条:

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

如下:

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

只获取捕获组1的内容。

pcregrep工具利用了你已经用过的所有语法,但是实现了你所需要的function。

参数-ogrep版本一样工作,如果它是裸露的,但它也接受pcregrep一个数字参数,这个参数表示你想要显示哪个捕获组。

有了这个解决scheme,脚本中只需要最less的修改就可以了。 您只需更换一个模块化实用程序,并调整参数。

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

我相信不可能只是grep

对于sed:

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

我会刺杀奖金,虽然:

 echo "$name.jpg" 

这是一个使用gawk的解决scheme。 这是我觉得我需要经常使用,所以我创build了一个函数

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

使用只是做

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

一个build议 – 你可以使用参数扩展从最后一个下划线开始删除名字的一部分,同样在开始时:

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

那么name将具有abc值。

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

如果你有bash,你可以使用扩展的globbing

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

要么

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