What is flow control in Bash?
That was the question
find . -type f -name \*.mp4 -exec sh -c 'ffprobe "$0" 2>&1 | grep -q 1920x1080 && echo "$0"' {} \; which will output all mp4 files that are 1920x1080 .
I do not understand why sh -c is. If I delete it, he will not find anything.
The author says
A new shell is required to control the flow inside the exec'd command.
but I suppose that I do not have enough fundamental knowledge to understand the answer.
Question
Can someone explain why sh -c should be there, and why it only works when ffprobe opens in a new shell?
The -exec parameter accepts a sequence of arguments:
find . -exec arg0 arg1 arg2 ... \; If you put the arguments in quotation marks
find . -exec "arg0 arg1 arg2" \; then "arg0 arg1 arg2" are considered as one argument. It was expected that the arg0 arg1 arg2 command with spaces would exist on your system instead of the arg0 with arg1 and arg2 parameters.
If you used find without sh -c , you would get the following:
find . -type f -name \*.mp4 -exec 'ffprobe "{}" 2>&1 | grep -q 1920x1080 && echo "{}"' \; This would mean that find would look for a command called ffprobe "$0" .... without passing any arguments - there is no such command. There is a command called ffprobe that takes arguments, and that is what you need. One possibility is to do something like this:
find . -type f -name \*.mp4 -exec ffprobe '$0' 2>&1 | grep -q 1920x1080 && echo '{}' \; However, this does not work, since output redirection 2>&1 and pipe | and the && statement will be handled differently than you want.
To get around this, they use a different shell. This is similar to creating a script to do the job:
find . -type f -name \*.mp4 -exec myscript {} \; But instead of a separate script, everything is all on one line.
find executes the arguments specified in exec as directly in the command (that is, it calls the first argument as an application with the following arguments as arguments to this command), without doing any processing on it, except for replacing {} with the file name. That is, it does not implement any shell functions, such as channel switching or input redirection. In your case, the command contains channels and input redirects. So you need to run the command through sh so that sh handle them.
The difference lies in the implementation of find . Find uses the flavors of the Linux (Unix (Posix)) fork() and exec() calls to create a new process and pass a command with arguments. The call to exec() launches an executable file (binary or shebang interpreted program), passing the arguments directly to it in the form of argument lines. These arguments are not processed.
Therefore, shells are not interpreted. For instance. $0 , 2>&1 and '|' etc. passed to fprobe as arguments. Not what you want. By adding sh -c , you tell find to execute the shell, passing it the rest for interpretation.
Note that you can get the same effect by placing your command in a shebang script and calling this script from find . Here the exec'ed script will load the shell.