简体   繁体   中英

What is Flow Control in Bash?

In this question the answer was

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 don't understand why sh -c is there. If I remove it, then it doesn't find anything.

The author says

The new shell is required to handle the flow control inside the exec'd command.

but I guess I am missing some fundamental knowledge to understand the answer.

Question

Can anyone explain why sh -c have to be there, and why it only works then ffprobe is opened in a new shell?

The -exec option takes a sequence of arguments:

find . -exec arg0 arg1 arg2 ... \;

If you put the arguments in quotes

find . -exec "arg0 arg1 arg2" \;

then "arg0 arg1 arg2" are treated as a single argument. It would expect a command called arg0 arg1 arg2 , with the spaces, to exist on your system, instead of a command called arg0 with parameters of arg1 and arg2 .

If you were to use find without the sh -c , then you would have this:

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" .... , passing no arguments -- there is no such command. There is a command called ffprobe , which takes arguments, and that is what you need. One possibility would be to do something like this:

find . -type f -name \*.mp4 -exec ffprobe '$0' 2>&1 |
   grep -q 1920x1080 && echo '{}' \;

However, that doesn't work, since the output redirection 2>&1 and the pipe | and the command sequence operator && would all be treated differently than what you want.

To get around this, they use another shell. This is similar to creating a script to do the work:

find . -type f -name \*.mp4 -exec myscript {} \;

But instead of a separate script, everything is all on one line.

find executes the arguments given to exec as a command directly (that is it invokes the first argument as an application with the following arguments as arguments to that command) without doing any processing on it other than replacing {} with the file name. That is it does not implement any shell features like piping or input redirection. In your case the command contains pipes and input redirections. So you need to run the command through sh , so that sh handles those.

The difference lies in the implementation of find . Find uses flavors of fork() and exec() calls of Linux/Unix (Posix) to spawn a new process and pass the command with arguments to it. The exec() call runs an executable (binary or shebang interpreted program), passing the arguments directly to it as argument strings. No processing of those arguments is done.

Therefore shell conventions are not interpreted. Eg the $0 , 2>&1 and '|' etc. are passed to fprobe as arguments. Not what you want. By adding the sh -c , you are telling find to exec a shell , passing the rest of the line to it for interpretation.

Note that you could get the same effect by putting your command in a shebang script and calling this script from find . Here the exec'ed script would load the shell.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM