使用grep进行正则表达式格式化

3

我正在为我的应用程序解析mp3文件的IDv3标签,并使用id3v2进行操作。例如,要获取mp3文件的专辑名称,命令如下:

id3v2 -R sample.mp3 | grep "TALB"

输出的内容是
TALB: Album Name

但是,我只想获取TALB的值,而不是整行内容,因此我按照以下方式将其传递给 sed

id3v2 -R sample.mp3 | grep "TALB" | sed 's/TALB: //'

输出

Album Name

那么,这是我的问题:

  1. 是否只能通过grep实现上述结果?如果是,我该怎么做?
  2. 即使我得到了我想要的结果,但当我寻找其他标签(如TCON)时,它返回example.com (255),而我只需要example.com。那么,我应该如何在grep中编写正则表达式以实现这种类型的筛选?

你不能仅使用 grep 来完成那个任务。就像你所做的那样,你必须使用 sed 或其他过滤器来剥离行中的部分内容。 - Diego Sevilla
@DiegoSevilla 那么,使用grep我只能得到行吗? - Gowtham
3个回答

4
您可以使用带有-P标志的Perl风格正则表达式:
grep -Po '(?<=TALB: ).*'

例如:

$ echo "TALB: Album Name" | grep -Po '(?<=TALB: ).*'
Album Name

(?<=TALB: ) 是一个 零宽度后顾断言.* 是您想匹配的其余内容的模式 - 在第一种情况下,是所有内容。如果您想在标记字符串之后仅匹配特定字符串(如第二个问题中所述),则可以更改此内容以适应您的特定数据。


是的,很好。第二个问题呢?请解释一下上面的代码。 - Gowtham
1
或者可以使用grep -Po 'TALB: \K.*',这可能更容易理解,特别是对于不熟悉前后查找断言的人。 - Adrian Frühwirth

2
这里有一种更动态的方法,只使用bash而不需要外部工具即可读取所有/所需标签并将其存储到同名变量中以供进一步使用。这种方法更快且能够获得您需要的所有标签,无需额外的成本/代码。

另一个好处是它是可移植的(<<<需要bash 2.05${foo,,}需要bash 4,但可以轻松地删除,其余部分符合POSIX)。例如,在OS X上,grep -P虽然非常方便,但已不再可用,因此如果可移植性是一个问题,则不太适合用于脚本。
#!/bin/bash

# read line-wise from stdin using colon ':' as additional delimiter.
# what comes before ':' gets stored in $key, what comes after in $value
while IFS=' \t:' read -r key value; do
    # check if the value of $key is a tag that we are interested in
    case ${key} in
        TALB|TCON|TFOO) ;; # $key is one of TALB, TCON or TFOO. let's continue
        *) continue     ;; # otherwise, ignore line and read next line
    esac

    # store $value in the variable named $key, e.g. if
    #   $key == "TCON" and $value == "Album Name"
    # this would create $tcon == "Album Name"
    # (${key,,} converts $key to lowercase during the process)
    read -r "${key,,}" <<< "${value}"
done <<__DATA__
TALB: Album Name
TCON: example.com (255)
TFOO: tfoo
TBAR: tbar
__DATA__

echo "TALB: $talb"
echo "TCON: ${tcon% (255)}"  # strip the trailing " (255)" from $tcon
echo "TFOO: $tfoo"
echo "TBAR: $tbar"

.

$ ./t.sh
TALB: Album Name
TCON: example.com
TFOO: tfoo
TBAR:

${foo,,}bash 4中的概念,将$foo转换为小写,以避免使用大写变量名称,但如果您没有bash 4,则可以直接放弃它并使用大写名称,或者使用tr将它们转换为小写。

while read line惯用语在这里有很好的解释。

不要像我使用heredoc来模拟id3v2的输出,而应使用进程替换id3v2的输出“管道”到循环中,例如:

while [...]; do
    [...]
done < <(id3v2 -R Idhayam.mp3)

谢谢你的帮助。但是,我不知道你在这里做什么,特别是与__DATA__变量有关,而且它似乎有点复杂。我肯定会在shell脚本中使用它,但不会这么复杂。 - Gowtham
@Gowtham,这并不是很复杂,我在我的代码中添加了一些注释。__DATA__ 部分在我给出的 heredoc URL 后面有解释,但如果你按照我在答案底部所解释的那样做,可以忽略它,因为我只是用它来生成测试输入,而不是实际使用 id3v2 - Adrian Frühwirth

1

我认为这是使用awk很好的工作:

id3v2 -R Idhayam.mp3 | awk -F': ' '/^TALB/ {print $2}'

这段代码会打印出冒号后面的部分。 -F 开关用于指定分隔符,该示例中为冒号和空格。如果在行首找到了 TALB,则打印第二列。

“TALB”部分可以放入变量中,例如:

id3v2 -R Idhayam.mp3 | awk -F': ' -v i="TALB" '"/^"i"/" {print $2}'

如果你只需要括号前面的部分,你可以这样做:
awk -F': ' -v i="TCON" '"/^"i"/" { split($2,a,"("); print a[1] }'

这段代码使用split函数创建了一个数组a,它保存了第二列的内容,并以(作为分隔符。然后它打印了该数组的第一个元素。


如果您喜欢使用sed,那么也无需使用grep。您可以像这样使用正则表达式:
sed -n 's/^TCON: \([^(]*\).*/\1/p'

这仅打印与"TCON"开头的行的括号前部分((之前的部分)相关的部分。将-n传递给sed表示默认情况下不会打印每一行。
  • ^TCON 匹配以 "TCON: " 开头的行
  • \( \) 捕获这些括号中间的内容(需要斜杠作为转义字符)
  • [^(] 不是 "(" 的字符
  • * 零个或多个它们
  • .* 吞掉行的其余部分
整行被替换为\1,它指的是先前捕获的部分。 p 表示打印。

谢谢你的回答,但它似乎比 grepsed 更复杂。 - Gowtham
@Gowtham 这取决于你认为什么是复杂的。就我个人而言,我认为零宽度回顾断言有点复杂!我已经添加了一个sed替代方案来解决你的第二个问题。 - Tom Fenech
是的,它确实有效,但您能否解释一下您在其中使用的正则表达式部分。 - Gowtham
@Gowtham,我已经在我的答案中添加了解释。 - Tom Fenech

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