The redirection needs to be quoted to avoid that the present shell interprets it.
But quoting it will also avoid the output of the command to be redirected.
The known solution to this is to call a shell:
find . -name '*.jpg' -exec sh -c 'echo "$1" >"$1".new' called_shell '{}' \;
In this case, the redirection (>
) is quoted on the present shell and works correctly inside the called shell. The called_shell
is used as the $0
parameter (the name) of the child shell (sh
).
That works well if a suffix is added the name of the file, but not if you use a prefix. For a prefix to work you need both to remove the ./
that find prepend to filenames with ${1#./}
and to use the -execdir
option.
You may (or may not) want to use the -iname
option so that files named *.JPG
or *.JpG
or other variations are also included.
find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
cjpeg -quality 80 "$1" > optimized_"${1#./}"
' called_shell '{}' \;
And, you may (or may not) also want to call the shell once per directory instead of once per file by adding a loop (for f do … ; done
) and a +
at the end:
find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
for f; do cjpeg -quality 80 "$f" > optimized_"${f#./}"; done
' called_shell '{}' \+
And, finally, as cjpeg
is able to directly write to a file, the redirection could be avoided as:
find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
for f; do cjpeg -quality 80 "$f" -outfile optimized_"${f#./}"; done
' called_shell '{}' \+
>
missing in the third code sample?{}
appears in a longer strings as such strings are typically not expanded.find
, once, and applied to thefind
command itself. The{}
has no special meaning in that context. The redirection is not an argument tofind
, and it certainly is not part of the-exec
clause.