find exec ‘{}’ not available after >

Exec allows us to either pass all arguments at once with {} + or to pass them one by one with {} ;

Now let’s say I want to rename all jpeg, no problem doing this:

find . ( -name '*.jpg' -o -name '*.jpeg' ) -exec mv '{}' '{}'.new ;

But if I need to redirect output, '{}' isn’t accessible after redirection.

find . ( -name '*.jpg' -o -name '*.jpeg' ) -exec cjpeg -quality 80 '{}' > optimized_'{}' ;

This wouldn’t work. I’d have to use a for loop, storing find’s output into a variable before using it. Let’s admit it, it’s cumbersome.

for f in `find . ( -name '*.jpg' -o -name '*.jpeg' )`; do cjpeg -quality 80 $f > optimized_$f; done;

So is there a better way?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

You could use bash -c within the find -exec command and use the positional parameter with the bash command:

find . ( -name '*.jpg' -o -name '*.jpeg' ) -exec bash -c 'cjpeg -quality 80 "$1" > "$(dirname "$1")/optimized_$(basename "$1")"' sh {} ;

That way {} is provided with $1.

The sh before the {} tells the inner shell its “name”, the string used here is used in e.g. error messages. This is discussed more in this answer on stackoverflow.

Method 2

You have an answer(https://unix.stackexchange.com/a/481687/4778), but here is why.

The redirection >, and also pipes |, and $ expansion, are all done by the shell before the command is executed. Therefore stdout is redirected to optimized_{}, before find is started.

Method 3

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 '{}' +

Method 4

cjpeg has an option that lets you write to a named file, rather than standard output. If your version of find supports the -execdir option, you can take advantage of that to make the redirection unnecessary.

find . ( -name '*.jpg' -o -name '*.jpeg' ) 
  -execdir cjpeg -quality 80 -outfile optimized_'{}' '{}' ;

Note: this actually assumes the BSD version of find, which appears to strip the leading ./ from the file name when exanding to {}. (Or conversely, GNU find adds ./ to the name. There’s no standard to say which behavior is “right”.)

Method 5

Create a script cjq80:

#!/bin/bash
cjpeg -quality 80 "$1" > "${1%/*}"/optimized_"${1##*/}"

Make it executable

chmod u+x cjq80

And use it in -exec:

find . ( -name '*.jpg' -o -name '*.jpeg' ) -exec cjq80 '{}' ;


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x