Perché questo non funziona per loop?

Questo è su Ubuntu 12.04 Sto cercando di capire come get ffmpeg per fare una conversione batch di FLACs in MP3, in modo ricorsivo. Se faccio cd in una directory e uso

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

che funziona perfettamente bene. Tuttavia, quando provo questo, non funziona:

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

Non fa nemless vomitare eventuali errori utili (ma qui è l'output comunque, non c'è bisogno di protestare):

 [email protected]:~/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 

Ho testato il command di find da solo e funziona come previsto, quindi il problema deve essere qualcosa a che fare con l'interazione tra find e find .

Sono consapevole che potrei fare qualcosa con l'opzione di ricerca -exec , ma non posso trovare alcun modo per sostituire la string come posso con un bash for loop, e preferisco non avere un paio di file.flac.mp3 s per affrontare, anche se potrebbero essere risolti con una semplice rename .

Le virgolette doppie intorno al command $(find ...) fanno per vedere l'output di Find come un unico nome di file, che ovviamente non è quello che vuoi.

Ci sono diversi modi per raggiungere quello che vuoi:

  • Fai trovare eseguire ffmpeg direttamente (senza tubazioni, senza loop):

     find . -type f -name *.flac -exec bash -c ' ffmpeg -i "$0" -c:a libmp3lame -q:a 2 "${0/%flac/mp3}" ' {} \; 
  • Usa find ... -print0 | xargs -0 ... find ... -print0 | xargs -0 ... anziché per, che è appositamente realizzato per questi scopi:

     find . -type f -name *.flac -print0 | xargs -0 -I {} bash -c ' ffmpeg -i "$0" -c:a libmp3lame -q:a 2 "${0/%flac/mp3}" ' {} 
  • Cambiare IFS solo per newline, utilizzare lo stesso command come prima, less le virgolette doppie:

     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 

    Il command unset IFS modifica l'IFS al suo valore predefinito (non necessario all'interno di uno script di shell, a less che non interferisca con i comandi successivi).

Si prega di leggere BashFAQ / 020 – Greg's Wiki . Per eseguire correttamente l'iterazione sull'output della find , ecco un metodo che dovrebbe far fronte a tutti i tipi di personaggi strani nei nomi di file (spazi, linee di nuovo, globbing, ecc.)

 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) 

Non dimenticare di citare "*.flac" per impedire l'espansione se ci sono file nella directory corrente terminata con .flac .

Oltre alle ottime risposte elencate qui, da allora ho trovato che GNU Parallel può farlo:

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

{.} elimina l'estensione del file dal nome del file. GNU Parallel utilizza newline come separatore per impostazione predefinita, quindi non c'è bisogno di utilizzare find ... -print0 | parallel -0 ... find ... -print0 | parallel -0 ... less che non si stia anticipando i nomi di file contenenti nuove righe.

Come suggerisce il suo nome, GNU Parallel esegue processi in parallelo – un process per nucleo CPU, per impostazione predefinita. Così potrebbe accelerare le cose un po '; non molto in questo caso particolare, dato che ffmpeg è multi-filettato da solo … ma ancora.