我在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

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

如果可能的话,我只想使用grep。如果不是,请不要使用Python或Perl等sed或类似的语言——我想从*nix纯粹的角度来攻击这个问题。

此外,作为一个超级酷的奖金,我很好奇我如何能在壳串?我捕获的组是存储在$name中的字符串“someename”,我想在它的末尾添加字符串“.jpg”,我可以cat $name '.jpg'吗?


当前回答

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

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

使用just do

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

其他回答

我相信在grep是不可能的

对话:

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

不过,我想尝试一下额外的奖励:

echo "$name.jpg"

下面的例子展示了如何使用正则表达式捕获组从文件名中提取3个字符序列:

for f in 123_abc_123.jpg 123_xyz_432.jpg
do
    echo "f:    " $f
    name=$( perl -ne 'if (/[0-9]+_([a-z]+)_[0-9a-z]*/) { print $1 . "\n" }' <<< $f )
    echo "name: " $name
done

输出:

f:     123_abc_123.jpg
name:  abc
f:     123_xyz_432.jpg
name:  xyz

因此,perl中的if-regex条件语句将同时过滤掉所有不匹配的行,对于那些匹配的行,它将应用捕获组(s),您可以使用$1,$2,…分别

这对于纯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。这将通过分隔符_将行解析为多个字段,并仅返回字段2(字段号从1开始)。

Unix的哲学是让工具做一件事,并做得很好,并结合它们来完成非平凡的任务,所以我认为grep + sed等是一种更Unix的做事方式:-)

如果你使用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中,等等。索引0是完全匹配。

你应该意识到,如果没有锚,这个正则表达式(以及使用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操作符(变长向后查找)使前面的模式匹配,但不包括结果中的匹配。固定长度的等效值是(?<=)-模式将包含在右括号之前。如果量词可以匹配不同长度的字符串(例如+,*,{2,4}),则必须使用\K。

(?=)操作符匹配固定长度或可变长度模式,称为“超前查找”。它也不包括结果中匹配的字符串。

为了使匹配不区分大小写,使用(?i)操作符。它影响着后面的图案,所以它的位置很重要。

正则表达式可能需要根据文件名中是否有其他字符进行调整。您将注意到,在本例中,我展示了一个在捕获子字符串的同时连接字符串的示例。

我更喜欢一行的python或perl命令,这两者通常都包含在主要的linux发行版中

echo $'
<a href="http://stackoverflow.com">
</a>
<a href="http://google.com">
</a>
' |  python -c $'
import re
import sys
for i in sys.stdin:
  g=re.match(r\'.*href="(.*)"\',i);
  if g is not None:
    print g.group(1)
'

处理文件:

ls *.txt | python -c $'
import sys
import re
for i in sys.stdin:
  i=i.strip()
  f=open(i,"r")
  for j in f:
    g=re.match(r\'.*href="(.*)"\',j);
    if g is not None:
      print g.group(1)
  f.close()
'