I have an album of 11 .flac
audio files. (edit: since this issue has been resolved, it's now clear that the precise names and content of the files are irrelevant, so I've renamed them):
> find . -name "*.flac"
./two.flac
./ten.flac
./nine.flac
./eight.flac
./seven.flac
./three.flac
./four.flac
./five.flac
./one.flac
./eleven.flac
./six.flac
I was converting these to .wav
files with a specific sample rate and bit depth. I used ffmpeg
in a Bash shell to do this. A command like this one works perfectly for each of the 11 files if I call it manually:
ffmpeg -i "./six.flac" -sample_fmt s16 -ar 44100 "./six.wav"
However, when I wrote a while
loop to run this command for each file:
find . -name "*.flac" | sed "s/\.flac$//" | while read f; do ffmpeg -i "$f.flac" -sample_fmt s16 -ar 44100 "$f.wav"; done
This worked, but only for 8/11 of the files, with ffmpeg
giving the following error messages for the three failures:
/ten.flac: No such file or directory
ree.flac: No such file or directory
ix.flac: No such file or directory
For ./ten.flac
, the leading .
in the relative file path was truncated, resulting in an absolute path, and the other two, ./three.flac
and ./six.flac
, lose even more characters, including from the start of their basenames. Some of the other eight files had ./
truncated, but this resulted in the correct relative path, so ffmpeg
was able to continue in these cases.
If I use the same loop structure, but with echo "$f"
instead of calling ffmpeg
, I see no problems with the output:
> find . -name "*.flac" | sed "s/\.flac$//" | while read f; do echo "$f"; done
./two
./ten
./nine
./seven
./three
./four
./five
./one
./eleven
./six
So I'm convinced the structure of the loop is fine, with "$f"
expanding how I expect it to at each iteration. Somehow, when passing "$f.flac"
into ffmpeg
, part of the string is truncated, but only for some of the files.
I just want to understand why my first attempt exhibited this behaviour. I'm not looking for an alternative way to loop over or convert the files (my second attempt, with a different kind of loop, was successful for all files).
edit: I accidentally discovered that piping yes |
into ffmpeg
seems to fix this issue. I added this so I wouldn't be prompted to overwrite existing .wav
files.
edit: Thanks @roaima for the explanation! Turns out that ffmpeg
and read
both inherit the same stdin handle, so ffmpeg
could consume characters from the start of each line before read
got a chance, thus mangling some of the file paths. This explains why yes | ffmpeg ...
worked, since it gave ffmpeg
a different stdin handle. The original loop works fine with ffmpeg -nostdin ...
. See http://mywiki.wooledge.org/BashFAQ/089.
9. Stranger.flac
when the file appears to be09. Stranger.flac
. Lots of things in bash/shell can strip leading 0s on numbers but I don't see anything you are doing that would, and it's inconsistent. – jesse_b Sep 22 '22 at 14:51sed
, but it makes no difference in this case, and certainly doesn't contribute to the substance of the question... – colmbaston Sep 22 '22 at 15:14find . -name "*.flac" | sed "s/\.flac//"
is identical to the loop withecho "$f"
in the post. – colmbaston Sep 22 '22 at 15:16sed
. – colmbaston Sep 22 '22 at 15:21IFS=0
would do that. – Kusalananda Sep 22 '22 at 17:27