0

Is there a way to pass more than one argument from find -exec to a bash -c sub-shell?

Given this working invocation which searches all videos matching a glob pattern (eg "*dogs.mp4) and then executing ffmpeg in a sub-shell with a check and warning in case the file already exists:

find . -iname "*.mp4" -exec bash -c 'if [[ -f "${1%.*}_540.mp4" ]]; then echo "Already exists: $1"; else ffmpeg -i "$1" -vf "scale=540:-1" "${1%.*}_540.mp4" ; fi ;' _ {} +

In order to avoid hard-coding the size I replaced 540 with $2 and then pass the size as parameter after {} (to _ {} 540 +) so that the command becomes:

find . -iname "*.mp4" -exec bash -c 'if [[ -f "${1%.*}_$2.mp4" ]]; then echo "Already exists: $1"; else ffmpeg -i "$1" -vf "scale=%2:-1" "${1%.*}_%2.mp4" ; fi ;' _ {} 540 +

The line above throws an error: find: -exec: no terminating ";" or "+":

My understanding is that {} is the placeholder for the file name and {} + (or {} \;) must be at the end of the command.

In this scenario how to pass an additional parameter to bash -c from the -exec clause?

ccpizza
  • 1,723

1 Answers1

2

You can't put an argument after the {} if you're using +; check the spec:

-exec ... Only a plus-sign that immediately follows an argument containing only the two characters "{}" shall punctuate the end of the primary expression. Other uses of the plus-sign shall not be treated as special.

Also, you are passing multiple args to the shell (using +) but only using $1 which means you're ignoring the rest of the files found. You need to put a loop inside your shell command. See https://unix.stackexchange.com/a/321753/135943 for some examples, but overall it looks like:

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' placeholder {} +

And you are "hard-coding" the size anyway. You may as well hardcode it in the actual shell command; it's easier then hardcoding it in the parameter list at the end. But if you insist, you could pass 540 in place of your underscore (i.e. in place of the "placeholder" in the above command), and access it at $0 from inside the shell command.


For the more general question, how to pass additional args to a shell command embedded in a find ... -exec command when you're passing all the found files to the shell command and want to process them in a loop, it can be done like so:

find dirname ... -exec sh -c 'arg1="$1"; arg2="$2"; shift 2; for f do somecommandwith "$f" "$arg1" "$arg2"; done' placeholder firstextraarg secondextraarg {} +

But the real question is, why would you want to do that? You can much more easily put the "firstextraarg" and "secondextraarg" values directly inside the shell command:

find dirname ... -exec sh -c 'for f do somecommandwith "$f" firstextraarg secondextraarg; done' placeholder {} +
Wildcard
  • 36,499
  • if the statement is terminated with {} \; then there is no need to loop over matches inside the subshell. + does some kind of speed optimization and in this specific case with + the subshell is not getting called for each single match – ccpizza Mar 01 '22 at 22:32
  • @ccpizza right, that's the purpose of the +. If you don't want multiple files passed to the command for each invocation you shouldn't be using +. See the spec (I provided the link) and search for "-exec" and you'll find this. – Wildcard Mar 01 '22 at 22:48
  • Thanks for confirming! in fact, I'll change it to find ... -print0 | xargs-0 ... so that it doesn't break with funny file names – ccpizza Mar 01 '22 at 22:54
  • 1
    @ccpizza find ... -exec ... {} + is completely safe from breakage regardless of special filenames but okay. (If that surprises you see additional background in http://unix.stackexchange.com/q/321697/135943) – Wildcard Mar 01 '22 at 22:58