Is it possible to nest a 'find -exec' within another 'find -exec'?

8,523

Solution 1

I would try using a single find like:

find .*/ -maxdepth 1 -type f -name '*.ini' -execdir md5sum {} +

or even (no find at all, just shell globbing)

md5sum .*/*.ini

although this lacks the -type f check so only works if you have no directories/non-files ending in .ini. If you do you could use

for x in .*/*.ini; do 
    if [ -f "$x" ]; then 
        md5sum "$x"
    fi
done

which would however lose the advantage of only needing one md5sum invocation.

Edit

For a general and safe method of chaining find, you can do something like

find <paths> <args> -print0 | xargs -0 -I{.} find {.} <args for second find> [etc.]

Solution 2

Your original problem does not require calling find recursively but I suppose that was not the point.

I believe it is not possible to call find recursively in the way you want.

The following is not calling find recursively (or nesting, whatever it is called) either, but can't you just take a result set of the first find and feed it to the second one? This is how I would instinctively do:

find `find ./ -maxdepth 1 -type d -name '.*'` \
    -maxdepth 1 -type f -name '*.ini' -exec md5sum {} \;

You could also use xargs for executing the second find.

Update:

I wanted to add that because most UNIX utilities take several file name arguments instead of one, you can usually avoid the -exec altogether:

md5sum `find \`find ./ -maxdepth 1 -type d -name '.*'\` -maxdepth 1 -type f -name '*.ini'`

When nesting backticks you just add backslashes \ before the inner ones.

If we imagine that md5sum takes only one filename argument, we can always wrap it in a for loop:

for f in `find \`find ./ -maxdepth 1 -type d -name '.*'\` -maxdepth 1 -type f -name '*.ini'`
do
    md5sum $f
done

Note that this becomes more difficult if file/directory names starting with - or containing a space are involved. UNIX utilities do not play nicely with them. In that case adding ./, -- or quotes is needed.

Obviously the original example is not a good one, because we could just do:

md5sum .*/*.ini
Share:
8,523
Peter.O
Author by

Peter.O

Updated on September 18, 2022

Comments

  • Peter.O
    Peter.O almost 2 years

    Something like the following is what I what I'm after, but my code doesn't work, no matter how I escape {} and + ;

    find ./ -maxdepth 1 -type d -name '.*' -exec \
        find {} -maxdepth 1 -type f -name '*.ini' -exec \
            md5sum \{\} \\; \;
    

    After seeing this Unix-&-Linux question, I found that the following code works, but it isn't nesting find as such, and I suspect there is a better way to do this particular job.

    find ./ -maxdepth 1 -type d -name '.*' \
    -exec bash -c 'for x; do
        find "$x" -maxdepth 1 -type f -name "*.ini" \
        -exec md5sum \{\} \;; \
    done' _ {} \+
    

    Is there some way to nest find -exec without the need to invoke a shell (as above), with all its whacky quoteing and escape constraints?

    Or can this be done directly in a single find command, using a blend of its many parameters?

    • Warren Young
      Warren Young almost 13 years
      While it may be possible to do what you're asking, when things get that complex, I switch to shell or Perl scripts. Your second code snippet is pretty much doing this, only with the shell script inline. Heroic one-liners are entertaining, but they're hard to understand, and thus hard to maintain. Unless this is a one-shot deal that you nevertheless somehow end up getting good at, I can't see a good reason to do it other than the intellectual challenge.
    • Peter.O
      Peter.O almost 13 years
      @Warren Young: I certainly don't think the concept is complex, but I assume you mean there is no simple way to do in with find, but if find can't do this, then why is find so revered(?) as the tool-to-use for finding files?... I've subseqeuntly found that find ./ -maxdepth 2 -path '.*/*.ini' -type f -exec md5sum {} \+ works fine in my situation (jw013's reference to -prune led me to this in the man page), but I wonder if it is a robust method(in genera). I've never really used find (in less than a year of Linux) as locate has done almost all I need, so it's unknown territory.
    • rozcietrzewiacz
      rozcietrzewiacz almost 13 years
      The -path test is exactly what I was going to suggest. With this, you should be able to do all that you want (sorry for the Ace Of Base association;) )
  • Peter.O
    Peter.O almost 13 years
    I get an error with the -f (f ?), and then another error with -execdir.. When I replace -execdir with -exec, and/or also replacing md5sum with print, I get nothing..
  • Peter.O
    Peter.O almost 13 years
    Thanks, for the alternative... but I'm more after a way of doing this using find ... It's not so much that I just want this example resolved, I'm looking for insights into the ways of find-fu ... maybe there more fluff than fu in find.. (I don't know, because I've virtually never used it), and this is the first situation I've really wanted to use it (for image files actually) and the -exec feature I've heard so much about seems to be not as all-powerful as its rep(?) alludes to... (+1 for the alternatives, though) .. but your find example just doesn't work (still)
  • Peter.O
    Peter.O almost 13 years
    I get this error for the find command: ... find: The relative path ~/bin' is included in the PATH environment variable, which is insecure in combination with the -execdir action of find. Please remove that entry from $PATH` .... So maybe it will work, but I must say I've been trying to get rid of that ~/bin for a while now... I"ll have to take as more serious look at it... I don't know where I"ve set it... any ideas where It may be lurking; the ~/bin in my PATH
  • jw013
    jw013 almost 13 years
    I think the power of find is best appreciated in situations which can't be done entirely with globs, but as those kinds of situations are rare I don't normally need find much.
  • Peter.O
    Peter.O almost 13 years
    Okay.. that's an interesting and good point (about using globs)...
  • jw013
    jw013 almost 13 years
    That's a built-in security feature of find. You shouldn't have relative paths in PATH if you want to use find -exec, and it's not a great idea anyways. I'd check ~/.profile, ~/.bash_profile and /etc/profile.
  • Peter.O
    Peter.O almost 13 years
    You've given a good overview of the situation, but all of the shown find methods get an error when a file/directory contains spaces... The md5sum .*/*.ini works fine... I'm starting to get the general feel that * Warren Young's* comment about things getting 'complex' for find happens early on in the game :), but I assume find comes into its own when the condition tests are more intricate, but as far as nesting -exec goes, I've pretty well dropped the idea, as it seems there are simpler ways to do it.. (but perl isn't "simple" to me (yet)...
  • Peter.O
    Peter.O almost 13 years
    Yes, that is my problem exactly :)
  • Peter.O
    Peter.O almost 13 years
    I've finally sorted out the ~/bin in my path problem, and I've now tried your find examples.. The last one, with xargs, is certainly up to the task, as were the non-find methods.. well done! thanks...
  • user unknown
    user unknown almost 13 years
    Yes, but not nesting 3 is not the same as not nesting 2. :)
  • Luke Savefrogs
    Luke Savefrogs almost 3 years
    The find ... | xargs ... method did the trick for me... I was trying to achieve exactly that. I needed a general solution that would look for a folder and THEN look for a specific file in that folder and execute a command on it