从Grep RegEx捕获组

Isa*_*aac 355 bash shell grep

我在sh(Mac OSX 10.6)中有这个小脚本来查看一系列文件.谷歌此时已停止提供帮助:

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

到目前为止(显然,对于你的shell大师)$name只有0,1或2,具体取决于是否grep发现文件名与提供的内容匹配.我想要的是捕获parens中的内容([a-z]+)并将其存储到变量中.

如果可能的话,我只想使用它grep.如果没有,请不要使用Python或Perl等,sed或类似的东西 - 我是shell的新手,并希望从*nix纯粹的角度来攻击它.

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

如果你有时间,请解释一下发生了什么.

Pau*_*ce. 469

如果你正在使用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
Run Code Online (Sandbox Code Playgroud)

将正则表达式放在变量中会更好.如果按字面意思包含,某些模式将无效.

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

您应该知道,如果没有锚点,这个正则表达式(以及使用的正则表达式grep)将匹配以下任何示例和更多,这可能不是您正在寻找的:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz
Run Code Online (Sandbox Code Playgroud)

要消除第二个和第四个例子,请使用这样的正则表达式:

^[0-9]+_([a-z]+)_[0-9a-z]*
Run Code Online (Sandbox Code Playgroud)

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

^[0-9]+_([a-z]+)_[0-9a-z]*$
Run Code Online (Sandbox Code Playgroud)

然后第三个例子也将被删除,因为点不在正则表达式中的字符之间,而美元符号表示字符串的结尾.请注意,第四个示例也会使此匹配失败.

如果您有GNU grep(大约2.5或更高版本,我认为,当\K添加运算符时):

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

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

所述(?=)操作者匹配固定的或可变长度的模式和被称为"超前".它也不包括结果中匹配的字符串.

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

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

  • 在这个答案中,我想提出一个特定的行,即"将正则表达式置于一个变量中更好.如果按字面意思包含,某些模式将不起作用." (41认同)
  • @FrancescoFrassinelli:一个例子是包含空白区域的模式.逃避是很尴尬,你不能使用引号,因为这会强制它从正则表达式到普通字符串.正确的方法是使用变量.在分配期间可以使用引号使事情变得更加简单. (4认同)
  • `/ K`操作员摇滚. (4认同)
  • “最好将正则表达式放在变量中。如果按字面意思包含某些模式,则某些模式将不起作用。” - 为什么会发生这种情况?有办法修复它们吗? (2认同)
  • @Brandon:它确实有效.你用的是什么版本的Bash?告诉我你正在做什么不起作用,也许我可以告诉你为什么. (2认同)
  • @mdelolmo:我的回答包括有关`grep`的信息。OP也接受了它,并对此进行了很多批评。谢谢你的反对。 (2认同)

Rob*_*obM 134

对于纯粹的grep,这是不可能的,至少不是一般的.

但是如果你的模式是合适的,你可以grep在管道中多次使用,首先将你的行减少到一个已知的格式,然后只提取你想要的位.(虽然工具喜欢cut并且sed在这方面要好得多).

假设为了论证你的模式有点简单:[0-9]+_([a-z]+)_你可以这样提取:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'
Run Code Online (Sandbox Code Playgroud)

第一个grep将删除任何与您的整体模式不匹配的行,第二个grep(已--only-matching指定)将显示名称的alpha部分.这只能起作用,因为这种模式是合适的:"alpha部分"具体到足以拉出你想要的东西.

(旁白:我个人使用grep+ cut来实现你的目标:echo $name | grep {pattern} | cut -d _ -f 2.这可以cut通过拆分分隔符将行解析为字段_,并返回字段2(字段编号从1开始)).

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

  • @ ghostdog74:这里没有争论将大量微小操作链接在一起通常比在一个地方完成所有操作效率低,但我坚持认为Unix哲学是很多工具在一起工作.例如,tar只是归档文件,它不压缩它们,并且因为它默认输出到STDOUT,你可以用netcat将它传送到网络,或用bzip2等压缩它.我认为这会强化常规和一般性Unix工具应该能够在管道中协同工作的精神. (7认同)
  • `for $ in $ files; do name =`echo $ f | grep -oEi'[0-9] + _([az] +)_ [0-9a-z]*'| 切-d _ -f 2`;`啊哈! (3认同)
  • 我不同意这种"哲学".如果你可以在不调用外部命令的情况下使用shell的内置功能,那么你的脚本的性能会快得多.有些工具在功能上有重叠.例如grep,sed和awk.所有这些都进行了字符串操作,但是awk在它们之上都很突出,因为它可以做更多的事情.实际上,所有那些链接命令,如上面的双greps或grep + sed可以通过一个awk进程来缩短它们. (2认同)

小智 88

我意识到答案已经被接受了,但是从"严格的*nix纯粹主义角度"来看,这似乎是正确的工具,pcregrep似乎还没有被提及.尝试更改线条:

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

以下内容:

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

仅获取捕获组1的内容.

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

该参数的-o工作方式与grep版本相同,如果它是裸的,但它也接受一个数字参数pcregrep,表示您要显示的捕获组.

使用此解决方案,脚本中只需要进行最少的更改.您只需将一个模块化实用程序替换为另一个,并调整参数.

有趣的注意事项:您可以使用多个-o参数按照它们在行上显示的顺序返回多个捕获组.

  • 我的`pcregrep`似乎不理解`-o`之后的数字:"-o1"中的"未知选项字母'1".当查看`pcregrep --help时,也没有提及该功能 (4认同)
  • `pcregrep` 8.41(在`Ubuntu 16.03`上安装`apt-get install pcregrep`)无法识别`-Ei`开关.但是,如果没有它,它的工作完美.在macOS上,通过`homebrew`(也是8.41)安装`pcregrep`,如上面提到的@anishpatel,至少在High Sierra上,`-E`开关也无法识别. (4认同)
  • `pcregrep`默认在"Mac OS X"中不可用,这是OP使用的 (3认同)
  • @WAF 抱歉,我想我应该在评论中包含该信息。我使用的是 Centos 6.5,pcregrep 版本显然很旧:`7.8 2008-09-05`。 (2认同)
  • 是的,非常有帮助,例如`echo'r123456 foo 2016-03-17'| pcregrep -o1'r([0-9] +)'123456` (2认同)
  • 在 macOS 上,“brew install pcre”。另请注意,Homebrew 的 zsh 依赖于 pcre,因此如果安装了 pcre,您可能已经有了 pcre。 (2认同)

cob*_*bal 25

我相信只有grep是不可能的

对于sed:

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

不过我会奖励奖金:

echo "$name.jpg"
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,那个"sed"解决方案不起作用.它只是打印出我目录中的所有内容. (2认同)

小智 25

str="1w 2d 1h"
regex="([0-9])w ([0-9])d ([0-9])h"
if [[ $str =~ $regex ]]
then
    week="${BASH_REMATCH[1]}"
    day="${BASH_REMATCH[2]}"
    hour="${BASH_REMATCH[3]}"
    echo $week --- $day ---- $hour
fi
Run Code Online (Sandbox Code Playgroud)

输出:1 --- 2 ---- 1


ops*_*psb 16

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

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

使用只是做

$ echo 'hello world' | regex1 'hello\s(.*)'
world
Run Code Online (Sandbox Code Playgroud)


mar*_*ton 6

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

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}
Run Code Online (Sandbox Code Playgroud)

那么name就会有价值abc

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