6
 -> find  .. -name bin  -exec  for file in {}/* ; do echo $file ; done  \;

   -bash: syntax error near unexpected token `do'

What is the proper syntax for this command ?

Tegra Detra
  • 5,016

3 Answers3

4

To use multiple statements, such as a for-loop, as the argument to -exec, one needs to invoke a shell, such as bash, explicitly:

find .. -name bin -exec bash -c 'for file in "$1"/* ; do echo "$file" ; done' none {}  \;

This is safe even for filenames that contain spaces or other hostile characters.

How bash -c works

One can invoke bash with a command of the form:

bash -c some_complex_commands arg0 arg1 arg2 ...

In this case, bash will execute whatever is in the string some_complex_commands. Those commands can make use of the usual shell positional parameters. The first argument after command, arg0 above, is assigned to $0, the second to $1, the third to $2, etc.

When one executes a normal shell script, $0 is the name of the script and $1 is the first argument that appears on the command line. In keeping with that tradition, the bash -c command was written to assign the file name, {} in find's notation, to $1. Since this script does not have a sensible name, none is assigned as a placeholder to $0.

John1024
  • 74,655
2

It looks like you've got things reversed from what you want. Try this:

for f in `find .. -name bin`
do
echo $f
done
John
  • 17,011
  • Nope . I want to know the syntax for what I placed in the original question . – Tegra Detra Apr 01 '15 at 17:55
  • @JamesAndino There is no need to loop inside find therefore it outputs founded elements like loop by itself – Costas Apr 01 '15 at 18:28
  • 1
    Your answer doesn’t even do anything except for printing a bunch of blank lines — $file is never set to anything.  Perhaps you don’t understand what James was trying to do. – Scott - Слава Україні Apr 01 '15 at 18:29
  • Correct on both counts, though I've just fixed the answer to address the first issue you noted. I still don't understand why he's looping inside the find's exec instead of looping over the entire output, but that may be because he never explained that. – John Apr 01 '15 at 18:36
  • I'm finding a set of folders then looping through the collection of commands in those bins . The problem with your answer is that if there are new line characters ( maybe spaces tabs etc I always forget ) the script will die a fiery death . Normally not a problem looping your own bin but I'll look for sub folders with a user chosen name and kaboom goes trash puter – Tegra Detra Apr 02 '15 at 05:22
  • @Scott if you read the question, the question just echos, so the question is about what a principle is re syntax. And this answer replies to that. – barlop May 08 '20 at 16:15
  • @barlop:  And if you read everything relevant, your level of understanding might increase. – Scott - Слава Україні May 11 '20 at 01:26
1

You can approximate the output of your pseudo-code there with find primitives as is:

find .. -path \*/bin/\* ! -name .\* 

...which should print only files/dirs with names that do not begin with a . and which are rooted at some level both in the parent directory, and, at some greater degree, in a dir named /bin.

In general I can think of all kinds of practical purposes for looping in a find -exec child process, but not a one in which I could consider it practical to do shelld glob on an argument passed by find. To do the same thing your pseudo-code does with printf - because its use can guarantee literal translation of arguments to output - you might do...

find .. -type d -name bin -exec sh -c '
    printf %s\\n "$0/"*' {} \;

...which does the printing and the globbing without the for loop. One difference between this and your example command, though, is that without the -type d specification and type of result matching the name bin will be echod - and so you're highly likely to see a lot of bin/* being written to stdout. Of course, even w/ -type d, there's no guarantee that the * will resolve - an empty directory or one containing only .files will render no matches and so you might see it anyway.

Note also that the example pseudo code, because it uses the {} \; primitives might be a lot slower than some other ways. We'll try the printf thing again like:

find .. -type d -name bin -exec sh -c '
    for d do printf %s\\n "$d/"*; done' -- {} +

...which still risks the empty glob case but instead of -execing a shell per match, it rather gathers as many arguments as it might reasonably pass off to another process at exec time and passes the lot to sh in "$@" its positional parameter array - which we can loop over with for d do printf....

Now if you wanted to do something other than just print results - which is what I would typically consider useful about looping in an -exec statement - you can fallback to the earlier -path example and combine it with -exec like...

find .. -path \*/bin/\* -exec sh -c '
    for arg do : something with "$arg"
done' -- {} +
mikeserv
  • 58,310
  • This is very useful . While waiting for an answer I actually figured to use globstar . I was actually going to ask about adapting my find command to find more specifically the files in bin and not just the bins to recurse . – Tegra Detra Apr 02 '15 at 05:25
  • @JamesAndino - You can do it without recursion: find .. -path '*/bin/*' ! -name . -prune ! -type d which should print only files other than type directory which reside in a directory named ../(*/)*bin. – mikeserv Apr 02 '15 at 06:09