I have a directory with some .flac files:

[test]$ ls

'test file (with $ign & parentheses).flac'  'test file with spaces.flac'

I want to run an ffmpeg test command on those files, i used find with -exec argument to achieve this:

find ./ -type f  -iregex '.*\.flac'  -exec sh -c 'ffmtest=$(ffmpeg -v error -i "{}" -f null - 2>&1);if [ -n "$ffmtest" ];then echo -e "{}\n" $ffmtest;fi ' \;

Little explanation for my code:

The find command will find the .flac files and pass the names to the ffmpeg command for testing. The ffmpeg command tests the file and return an error string to stdout(because of the redirection at the end) or nothing if no error is found, the if statement following the ffmpeg command is used so that if ffmtest variable contains an error then print the name of the file with the error followed by the error string(if the file contains no error then nothing will be printed).

My code works as expected but it fails if the filename contains a $ sign followed by a letter so for the test files mentioned above the output i get is this

[test]$ find ./ -type f  -iregex '.*\.flac'  -exec sh -c 'ffmtest=$(ffmpeg -v error -i "{}" -f null - 2>&1);if [ -n "$ffmtest" ];then echo -e "{}\n" $ffmtest;fi ' \;
./test file (with  & parentheses).flac
 ./test file (with & parentheses).flac: No such file or directory

you can see that the test file with no $ sign was tested properly and ffmpeg didn't output any errors but it failed to even open the file with the $ sign in the name, you can see from the output of ffmpeg and echo that the filename was interpreted and the $ sign followed by the letters were treated as a variable.

So how can I tell find to escape such characters ?

  • quote everything. Feb 22 '20 at 13:29
  • @KamilMaciorowski the answers below are both correct, I've chose the solution by the user Fox because it was minimal and closer to my original code. Feb 22 '20 at 22:47

The {} is replaced literally by the filename, so put it somewhere that it doesn't get parsed specially. Since you're already using sh -c, the argument position seems ideal:

find ./ -type f  -iregex '.*\.flac'  -exec sh -c 'ffmtest=$(ffmpeg -v error -i "$1" -f null - 2>&1);if [ -n "$ffmtest" ];then echo -e "$1\n" $ffmtest;fi' -- {} \;
find . -type f  -iregex '.*\.flac'  -exec sh -c '
  for file do
    ffmtest=$(ffmpeg -v error -i "$file" -f null - 2>&1)
    if [ -n "$ffmtest" ]; then
      printf "%s\n" "$file" " $ffmtest"
  done' sh {} +

A few rules:

  • never embed the {} is the code argument passed to sh (or any language interpreter), that would amount to a code injection vulnerability (it's also not portable). In your example, it wasn't so bad. But if the file had been called $(reboot).flac for instance, it could have had more severe consequences. Here, we're passing {} (with + so that as many are passed as possible to sh) for find to expand in the inline script's argument, not in the code of that inline script.
  • always quote parameter expansions in list contexts.
  • don't use echo for arbitrary data.

Not the answer you're looking for? Browse other questions tagged or ask your own question.