find command -exec

I'd like to stream multiple commands together with the -exec action of the find command. I came across the sh -c "..." construction. Things went well for normally named files, but the odd cases didn't. Is there any way of getting any filename to work with this construct?

The original context:

How can I find all geotagged photos with Yosemite 10.10.5 FINDER


It would seem that if find returned an escaped version of the file name, all would be better


mac $  find /Users/mac/exifin  -exec ls {} \;
!                        one.nef
!"$&()*,:;<=>?@[\]^`{|}                other.txt
!"$.dng                        photocardwithiPhone copy.dng
!$&"()*,:;<=>?@[\]^`{|}.dng            photocardwithiPhone-copy.dng
!$.dng                        photocardwithiphonesmallsize copy.nef
Screen Shot 2018-02-25 at 1.44.02 PM copy.nef    single'quote.dng
none.dng                    {}
/Users/mac/exifin/!
/Users/mac/exifin/!"$&()*,:;<=>?@[\]^`{|}
/Users/mac/exifin/!"$.dng
/Users/mac/exifin/!$&"()*,:;<=>?@[\]^`{|}.dng
/Users/mac/exifin/!$.dng
/Users/mac/exifin/none.dng
/Users/mac/exifin/one.nef
/Users/mac/exifin/other.txt
/Users/mac/exifin/photocardwithiPhone copy.dng
/Users/mac/exifin/photocardwithiPhone-copy.dng
/Users/mac/exifin/photocardwithiphonesmallsize copy.nef
/Users/mac/exifin/Screen Shot 2018-02-25 at 1.44.02 PM copy.nef
/Users/mac/exifin/single'quote.dng
/Users/mac/exifin/{}
mac $  find /Users/mac/exifin  -exec sh -c " ls {}" \;
!                        one.nef
!"$&()*,:;<=>?@[\]^`{|}                other.txt
!"$.dng                        photocardwithiPhone copy.dng
!$&"()*,:;<=>?@[\]^`{|}.dng            photocardwithiPhone-copy.dng
!$.dng                        photocardwithiphonesmallsize copy.nef
Screen Shot 2018-02-25 at 1.44.02 PM copy.nef    single'quote.dng
none.dng                    {}
/Users/mac/exifin/!
sh: -c: line 0: unexpected EOF while looking for matching ``'
sh: -c: line 1: syntax error: unexpected end of file
sh: -c: line 0: unexpected EOF while looking for matching `"'
sh: -c: line 1: syntax error: unexpected end of file
sh: -c: line 0: unexpected EOF while looking for matching ``'
sh: -c: line 1: syntax error: unexpected end of file
/Users/mac/exifin/!$.dng
/Users/mac/exifin/none.dng
/Users/mac/exifin/one.nef
/Users/mac/exifin/other.txt
ls: /Users/mac/exifin/photocardwithiPhone: No such file or directory
ls: copy.dng: No such file or directory
/Users/mac/exifin/photocardwithiPhone-copy.dng
ls: /Users/mac/exifin/photocardwithiphonesmallsize: No such file or directory
ls: copy.nef: No such file or directory
ls: /Users/mac/exifin/Screen: No such file or directory
ls: 1.44.02: No such file or directory
ls: 2018-02-25: No such file or directory
ls: PM: No such file or directory
ls: Shot: No such file or directory
ls: at: No such file or directory
ls: copy.nef: No such file or directory
sh: -c: line 0: unexpected EOF while looking for matching `''
sh: -c: line 1: syntax error: unexpected end of file
/Users/mac/exifin/{}
mac $  find /Users/mac/exifin  -exec sh -c " ls '{}' " \;
!                        one.nef
!"$&()*,:;<=>?@[\]^`{|}                other.txt
!"$.dng                        photocardwithiPhone copy.dng
!$&"()*,:;<=>?@[\]^`{|}.dng            photocardwithiPhone-copy.dng
!$.dng                        photocardwithiphonesmallsize copy.nef
Screen Shot 2018-02-25 at 1.44.02 PM copy.nef    single'quote.dng
none.dng                    {}
/Users/mac/exifin/!
/Users/mac/exifin/!"$&()*,:;<=>?@[\]^`{|}
/Users/mac/exifin/!"$.dng
/Users/mac/exifin/!$&"()*,:;<=>?@[\]^`{|}.dng
/Users/mac/exifin/!$.dng
/Users/mac/exifin/none.dng
/Users/mac/exifin/one.nef
/Users/mac/exifin/other.txt
/Users/mac/exifin/photocardwithiPhone copy.dng
/Users/mac/exifin/photocardwithiPhone-copy.dng
/Users/mac/exifin/photocardwithiphonesmallsize copy.nef
/Users/mac/exifin/Screen Shot 2018-02-25 at 1.44.02 PM copy.nef
sh: -c: line 0: unexpected EOF while looking for matching `''
sh: -c: line 1: syntax error: unexpected end of file
/Users/mac/exifin/{}
mac $

Mac mini, OS X Yosemite (10.10.5), Fall 2014; iPhone 4 7.1.2

Posted on Feb 28, 2018 12:31 PM

Reply
Question marked as Top-ranking reply

Posted on Mar 19, 2018 10:19 AM

Hi Robert,


I see a couple of issues with your attempt and of course this is conceptual, you would not list files found by find like this.


First: use single (strong) quotes around your command string. This will prevent the parent shell from attempting any expansion.


find ... -exec bash -c 'command string'


Second: do not place the find placeholder- {} inside the command string. It is placed as an argument after the command string


find ... -exec bash -c 'command string' sh {} +


Now sh is $0, where standard error is sent, which is also the name of the inline script. The find placeholder content now becomes positional parameters $1 $2 and so on.


To access the positional parameter we use a for loop and to put it all together->


find /Users/mac/exifin -exec bash -c ' for file

do

ls "$file"

done

' sh {} +



Note: once again we quote the variable in the loop to prevent globbing, word spliting, and keep the literal meaning of any meta-characters found in the file names. Now you can find ways to break this....:-)

7 replies
Question marked as Top-ranking reply

Mar 19, 2018 10:19 AM in response to rccharles

Hi Robert,


I see a couple of issues with your attempt and of course this is conceptual, you would not list files found by find like this.


First: use single (strong) quotes around your command string. This will prevent the parent shell from attempting any expansion.


find ... -exec bash -c 'command string'


Second: do not place the find placeholder- {} inside the command string. It is placed as an argument after the command string


find ... -exec bash -c 'command string' sh {} +


Now sh is $0, where standard error is sent, which is also the name of the inline script. The find placeholder content now becomes positional parameters $1 $2 and so on.


To access the positional parameter we use a for loop and to put it all together->


find /Users/mac/exifin -exec bash -c ' for file

do

ls "$file"

done

' sh {} +



Note: once again we quote the variable in the loop to prevent globbing, word spliting, and keep the literal meaning of any meta-characters found in the file names. Now you can find ways to break this....:-)

Mar 18, 2018 4:47 PM in response to rccharles

You are subjecting your file name to extra levels of special character processing when you pass the command to the shell to process.


-exec ls {}


is going to fork and exec the ls command and pass it argv[0] == ls, argv[1] == a_filename

There will be no special character processing on the file name.


-exec sh -c "ls {}"


find is going to fork and exec the sh command and pass it argv[0] == sh, argv[1] == -c, argv[2] == ls a_filename

The "..." will have been stripped off by the shell which is parsing the find command line.

Then the sh command is going to parse ls a_filename, warts and all. So any spaces will look like a token separator. Any shell command line special character will be processed by sh. You saw the results.


-exec sh -c "ls '{}'"


find is again going to fork and exec the sh command and pass it arv[0] == sh, argv[1] == -c and argv[2] == ls 'a_filename'

Again the "..." will have been stripped off by the shell which is parsing the find command line

then the sh command is going to parse ls 'a_filename'. Space will be protected, and because it is a single quote pair most special characters will be ignored, but any ' found in the filename will be matched to the leading ' and that will mean the trailing ' will be left without a matching paired '

And a \ will have special meaning in a '...' string.


Because you are trying to use every possible character in a file name, you will have a lot of difficulties trying to use the sh command.

Mar 19, 2018 10:25 AM in response to Mark Jalbert

You could get this approach to work, but I decided it was better to use applescript. Not all these gotchas.


Thanks for your clarification of the finer points of bash.


Well, I do my bash scripting based on known to work templates.

Questions?

-- what's with the trailing +

-- what happened to the trailing \; never understood why the \; was needed in the first place.

bash even gives you a warning message when you leave it out .


mac $   
find /Users/mac/exifin -exec bash -c ' for file



> 



> 
do



> 



>     
ls "$file"



> 



> 
done



> 



> 
' sh {} +


!   




 one.nef


!"$&()*,:;<=>?@[\]^`{|}   


 other.txt


!"$.dng   




 photocardwithiPhone copy.dng


!$&"()*,:;<=>?@[\]^`{|}.dng   

 photocardwithiPhone-copy.dng


!$.dng   




 photocardwithiphonesmallsize copy.nef



Screen Shot 2018-02-25 at 1.44.02 PM copy.nef    single'quote.dng



\ \&.txt   



 {}



none.dng
/Users/mac/exifin/!
/Users/mac/exifin/!"$&()*,:;<=>?@[\]^`{|}
/Users/mac/exifin/!"$.dng
/Users/mac/exifin/!$&"()*,:;<=>?@[\]^`{|}.dng
/Users/mac/exifin/!$.dng
/Users/mac/exifin/\ \&.txt
/Users/mac/exifin/none.dng
/Users/mac/exifin/one.nef
/Users/mac/exifin/other.txt
/Users/mac/exifin/photocardwithiPhone copy.dng
/Users/mac/exifin/photocardwithiPhone-copy.dng
/Users/mac/exifin/photocardwithiphonesmallsize copy.nef
/Users/mac/exifin/Screen Shot 2018-02-25 at 1.44.02 PM copy.nef
/Users/mac/exifin/single'quote.dng
/Users/mac/exifin/{}
mac $


R

Mar 18, 2018 6:43 PM in response to rccharles

The following find command will locate a single file in a specified search directory, output its full path, and get the name string (not the path) of the parent folder, which it concatenates onto the destination folder, and then runs ditto to copy the file to the destination in its original folder hierarchy.

find $HOME/Test -type f -name 'nstring.m' -print -exec bash -c 'echo "$(basename "${1%/*}")"' -- {} \; | xargs -L2 bash -c 'ditto -V "${1}" ~/Desktop/Backups/"${2}/"' --

Mar 19, 2018 10:39 AM in response to rccharles

ASC had trouble with the crazy characters too!


mac $       find /Users/mac/exifin -exec bash -c ' for file
>
>     do
>
>         ls "$file"
>
>     done
>
>     ' sh {} +
!      one.nef
!"$&()*,:;?@[\]^`{|}    other.txt
!"$.dng      photocardwithiPhone copy.dng
!$&"()*,:;?@[\]^`{|}.dng   photocardwithiPhone-copy.dng
!$.dng      photocardwithiphonesmallsize copy.nef
Screen Shot 2018-02-25 at 1.44.02 PM copy.nef    single'quote.dng
none.dng
/Users/mac/exifin/!
/Users/mac/exifin/!"$&()*,:;<=>?@[\]^`{|}
/Users/mac/exifin/!"$.dng
/Users/mac/exifin/!$&"()*,:;<=>?@[\]^`{|}.dng
/Users/mac/exifin/!$.dng
/Users/mac/exifin/\ \&.txt
/Users/mac/exifin/none.dng
/Users/mac/exifin/one.nef
/Users/mac/exifin/other.txt
/Users/mac/exifin/photocardwithiPhone copy.dng
/Users/mac/exifin/photocardwithiPhone-copy.dng
/Users/mac/exifin/photocardwithiphonesmallsize copy.nef
/Users/mac/exifin/Screen Shot 2018-02-25 at 1.44.02 PM copy.nef
/Users/mac/exifin/single'quote.dng
/Users/mac/exifin/{}
mac $

Mar 19, 2018 6:02 PM in response to Mark Jalbert

Reply to Mark Jalbert


You approach '...' approach is going to have the same problems the 'sh' approach experiences. Namely when a file name contains ' it will screw up the paired '...' because there is going to be command line parsing that is going to see the ' in the file name and think it is the end quote, which it is not.


If you want to have multiple shell commands play with each file name, I suggest you use something like the following in a 'bash' script (bash because it has process substitution):


while read file

do

echo "$file"

some_command "$file"

if [[ "$file" = something* ]]; then

...do something...

fi

done < <(find /Users/mac/exifin -print)

As long as you always use "$file" the ' inside will not cause any problems. And you do not use the 'eval' command on a line containing "$file"

This thread has been closed by the system or the community team. You may vote for any posts you find helpful, or search the Community for additional answers.

find command -exec

Welcome to Apple Support Community
A forum where Apple customers help each other with their products. Get started with your Apple Account.