Subshell arguments in -exec parameter for find(1)

bashfindunix

Why doesn't

find . -type f -exec echo $(file={}; echo ${file:0:5}) \;

give the first five characters of the file, while this works:

find . -type f -exec bash -c 'echo ${1:0:5}' funcname {} \;

Background:

I'm trying to batch convert a tree full of images to thumbnails, and I want to rename them on the fly by adding '_thumb' at the end of the filename but before the extension. This renaming process is easy for one file:

file='I am a picture.jpg'
mv \"$file\" \"${file%\.*}_thumb.${file##*\.}\"

(the second line expands to mv "I am a picture.jpg" "I am a picture_thumb.jpg")

but when I try to encapsulate this command in the -exec parameter of find(1) I cannot manipulate the filename given by find (examples simplified):

find . -type f -exec ${{}:0:5}) \;

gives

bash: ${{}:0:5}: bad substitution

Using a subshell I get a bit further:

find . -type f -exec echo $(file={}; echo ${file:0:5}) \;

this does echo the filename, but does not execute the string manipulation for some reason.

I finally found the solution in this SO post:

find . -type f -exec bash -c 'echo ${1:0:5}' funcname {} \;

but I don't understand why this would work when the $(...) construct does not.

Best Answer

exec does what it says: uses exec(). It doesn't involve a shell unless it's told to (-exec bash ...), and if it did you would still need single quotes to keep your interactive shell from interpreting the variable interpolations. (The shell is not magic and does not know that you intended those to be interpolated by something else.)


For example, when you use the following command:

find . -type f -exec echo $(file={}; echo ${file:0:5}) \;

your shell first performs process substitution by executing $(file={}; echo ${file:0:5}), which simply outputs {}, then executes the final command:

find . -type f -exec echo {} \;
Related Question