如何在Bash脚本中递归地使用foreach遍历*.mp3文件?

12
以下代码可以在当前文件夹中正常工作,但我希望它也可以扫描子文件夹。
for file in *.mp3

do

echo $file

done

1
find . -name \*.mp3 -exec yada yada yada - Marc B
5个回答

9
有很多方法可以解决这个问题。我会自己调用查找命令:
for file in $(find . -name '*.mp3') do
  echo $file
  TITLE=$(id3info "$file" | grep '^=== TIT2' | sed -e 's/.*: //g')
  ARTIST=$(id3info "$file" | grep '^=== TPE1' | sed -e 's/.*: //g')
  echo "$ARTIST - $TITLE"
done

如果您的文件名中有空格,则最好使用-print0选项来查找;一种可能的方法如下:
find . -name '*.mp3' -print0 | while read -d $'\0' file
do
  echo $file
  TITLE=$(id3info "$file" | grep '^=== TIT2' | sed -e 's/.*: //g')
  ARTIST=$(id3info "$file" | grep '^=== TPE1' | sed -e 's/.*: //g')
  echo "$ARTIST - $TITLE"
done

另外一种方法是保存和恢复 IFS。感谢David W.的评论,特别是指出while循环版本也具有一个好处,即它将正确处理非常大量的文件,而第一个版本将使用$(find)展开成for循环,会在某个时刻无法工作,因为shell扩展有限制。


是的。可以通过更改IFS或使用-print0来解决这个问题。我会编辑一个解决方案进去。 - drquicksilver
如果我找到1,000,000个文件怎么办?发生的事情是shell会扩展'$(find . -name "*.mp3)子句并将其替换为我的for`。我的命令行缓冲区不可能那么大,结果就是我的查找将会简单失败。在使用shell扩展到for循环时,您应该非常小心。 - David W.
1
@DavidW。将管道传递到while循环中也可以解决这个问题吗? - drquicksilver
3
你的第二个方案是最好的方法。 - David W.
为什么你要把错误的代码放在正确的代码之前?那些匆忙寻找答案的人往往是从上到下浏览(并不总是非常仔细地阅读)。 - Charles Duffy
在我看来,最好的答案。所选答案在给出实际答案之前有很多“文字”。 - rafark

9

太多的答案使用shell扩展来存储find命令的结果。这不是你轻易应该做的事情。

假设我有30,000首歌曲,这些歌曲的标题平均约为30个字符。现在先不要考虑空格的问题。

我的find命令将返回超过1,000,000个字符,很可能我的命令行缓冲区不够大。如果我做了这样的事情:

for file in $(find -name "*.mp3")
do
    echo "some sort of processing"
done

除了文件名中的空格之外,问题在于命令行缓冲区将简单地放弃从find中的溢出内容。甚至可能完全静默地失败。
这就是xargs命令被创建的原因。它确保命令行缓冲区永远不会溢出。它将执行跟随xargs的命令,直到保护命令行缓冲区的次数足够多:
$ find . -name "*.mp3" | xargs ...

当然,这种使用方式仍会受到空格的限制,但是现代的 xargsfind 实现了一种处理此问题的方法:
$ find . -name "*.mp3 -print0 | xargs --null ...

如果你能保证文件名中不会含有制表符、\n(或双空格),那么将查找命令的输出传递给 while 循环更为优秀:

find . -name "*.mp3" | while read file
do

管道将在命令行缓冲区填满之前将文件发送到while read。更好的是,read file会读取整行,并将该行中找到的所有项目放入$file中。它并不完美,因为read仍然会在空格处断开,所以文件名如下:
I will be in \n your heart in two lines.mp3
I   love song names with     multiple spaces.mp3
I \t have \t a \t thing \t for \t tabs.mp3.

仍然会失败。变量$file将把它们视为:

I will be in 
your heart in two lines.mp3
I love song names with multiple spaces.mp3
I have a thing for tabs.mp3.

为了解决这个问题,您需要使用find ... -print0将null作为输入分隔符。然后要么更改IFS以使用null,要么在BASH shell的while read语句中使用-d\0参数。

2
+1. 如果有人输入一个尝试解析指向正确答案的ls的代码片段,应该有一个巨大的红色“在继续之前请阅读此内容”标志。看起来这种问题的数量是如此之大,以至于它实际上会节省提问者和这里的乐于助人的人们很多时间。鉴于这种问题的错误答案数量,哦,天哪... - Adrian Frühwirth

5

这适用于大多数文件名(包括空格),但不能处理换行符、制表符或双空格。

find . -type f -name '*.mp3' | while read i; do
   echo "$i"
done

这适用于所有文件名。

find . -type f -name '*.mp3' -print0 | while IFS= read -r -d '' i; do
   echo "$i"
done

但如果你只想运行一个命令,可以使用 xargs 命令,例如:

find . -type f -name '*.mp3' -print0 | xargs -0 -l echo

4
find . -name *.mp3 -exec echo {} \;

字符串{}在命令参数中的所有出现位置都将被当前正在处理的文件名替换,而不仅仅是在独立的参数中,这与某些版本的find命令不同。请查看find man手册以获取更多信息:http://unixhelp.ed.ac.uk/CGI/man-cgi?find


-1

看起来你正在寻找find命令。我还没有测试过,但大致是这样的:

files=(`find . -name *.mp3`)
for file in "${files[@]}"; do
    echo $file TITLE="id3info "$file" | grep '^=== TIT2' | sed -e 's/.*: //g'" ARTIST="id3info "$file" | grep '^=== TPE1' | sed -e 's/.*: //g'"
done

编辑:使用数组可以使命令对于文件名中包含空格的文件更加安全。


这是完全不安全和有缺陷的(无论编辑声称什么):它确实会在包含空格或通配符字符的文件名中出现问题。 - gniourf_gniourf

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