为什么这个 for 循环不起作用?

为什么这个 for 循环不起作用?

这是在 Ubuntu 12.04 上,我试图弄清楚如何让 ffmpeg 以递归方式批量将 FLAC 转换为 MP3。如果我cd进入一个目录并使用

for f in *.flac; do ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"; done

运行得很好。但是,当我尝试这样做时,它不起作用:

for f in "$(find . -type f -name *.flac)"; do ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"; done

它甚至没有抛出任何有用的错误(但无论如何这里是输出,无需抱怨):

evilsoup@enchantment:~/Music/Jean Sibelius$ for f in "$(find . -type f -name *.flac)"; do ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"; done
ffmpeg version git-2012-12-18-b7e085a Copyright (c) 2000-2012 the FFmpeg developers
  built on Dec 18 2012 19:23:11 with gcc 4.6 (Ubuntu/Linaro 4.6.3-1ubuntu5)
  configuration: --enable-gpl --enable-libfaac --enable-libfdk-aac --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-librtmp --enable-libtheora --enable-libvorbis --enable-libvpx --enable-x11grab --enable-libx264 --enable-nonfree --enable-version3
  libavutil      52. 12.100 / 52. 12.100
  libavcodec     54. 80.100 / 54. 80.100
  libavformat    54. 49.102 / 54. 49.102
  libavdevice    54.  3.102 / 54.  3.102
  libavfilter     3. 28.100 /  3. 28.100
  libswscale      2.  1.103 /  2.  1.103
  libswresample   0. 17.102 /  0. 17.102
  libpostproc    52.  2.100 / 52.  2.100
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/02. Symphony No.1.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/03. Symphony No.1.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/stripped2.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/05. Symphony No.1.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/stripped3.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/09. Andante festivo.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/08. Symphony No.3.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/01. Finlandia.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/07. Symphony No.3.flac
./Symphonies 1, 2, 3 & 5

我已经单独测试了该命令,并且它可以按预期工作,因此问题与和find之间的交互有关。findfor

我知道我可以使用find-exec选项来做一些事情,但是我找不到任何方法来进行字符串替换,就像使用 bashfor循环一样,而且我宁愿不处理一堆file.flac.mp3s,即使它们可以用一个简单的 来修复rename

答案1

命令周围的双引号$(find ...)使得 find 的输出被视为一个文件名,这显然不是您想要的。

有几种方法可以实现你想要的:

  • 让 find 直接执行 ffmpeg(无管道,无循环):

    find . -type f -name *.flac -exec bash -c '
       ffmpeg -i "$0" -c:a libmp3lame -q:a 2 "${0/%flac/mp3}"
    ' {} \;
    
  • 使用find ... -print0 | xargs -0 ...而不是 for,它专门用于以下目的:

    find . -type f -name *.flac -print0 | xargs -0 -I {} bash -c '
        ffmpeg -i "$0" -c:a libmp3lame -q:a 2 "${0/%flac/mp3}"
    ' {}
    
  • 改变独立财务顾问仅换行,使用与以前相同的命令,减去双引号:

    IFS=$'\n'
    for f in $(find . -type f -name *.flac); do
        ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"
    done
    unset IFS
    

    该命令unset IFS将 IFS 更改回其默认值(在 shell 脚本中不需要,除非它会干扰后续命令)。

答案2

请阅读BashFAQ/020 - Greg 的维基。为了正确地迭代的输出find,这里有一个方法可以处理文件名中的各种奇怪字符(空格、换行符、通配符等)。

while IFS= read -r -d '' file; do
   ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"
done < <(find . -type f -name "*.flac" -print0)

"*.flac"如果当前目录中有以 结尾的文件,请不要忘记加上引号以防止扩展.flac

答案3

除了这里列出的非常好的答案之外,我还发现 GNU Parallel 可以做到这一点:

find . -type f -name "*.flac" | parallel ffmpeg -i {} -c:a libmp3lame -q:a 2 {.}.mp3

{.}find ... -print0 | parallel -0 ...从文件名中删除文件扩展名。GNU Parallel 默认使用换行符作为分隔符,因此除非您预计文件名包含换行符,否则无需使用。

顾名思义,GNU Parallel 并行运行进程 - 默认情况下,每个 CPU 核心运行一个作业。因此,它可能会稍微加快速度;在这种情况下,速度不会加快太多,因为 ffmpeg 本身是多线程的……但仍然如此。

相关内容