find with -execdir

For every matching file (i.e. every directory), find switches to the directory that contains it (i.e. its parent directory) and executes the specified command. Since the command doesn't use the name of the match, it's never going to act on all the directories. For this particular directory tree, you're doing

(cd . && touch foo)        # because ./a matches
(cd ./a && touch foo)      # because ./a/b matches
(cd ./a/b && touch foo)    # because ./a/b/c matches

To create a file in every directory, you can simply use -exec instead of -execdir, provided your implementation of find allows {} inside an argument (most do, and in particular I think all the ones ):

find . -type d -exec touch {}/foo +

For POSIX portability, you would need to do the assembling of the directory name and the file base name manually.

find . -type d -exec sh -c 'touch "$0/foo"' {} \;

or (slightly faster)

find . -type d -exec sh -c 'for d; do touch "$d/foo"; done' _ {} +

Alternatively, you can use bash's recursive wildcard matching. Beware that (unlike the corresponding feature in ksh and zsh, and unlike your find command) bash recurses under symbolic links to directories.

shopt -s globstar
for d in **/*/; do touch -- "$d/foo"; done

A zsh solution:

touch ./**/(e\''REPLY+=foo'\')

The command executes in every directory that contains a matched file. Since c doesn't contain a directory, it doesn't match and thus won't be run there.

The solution is to add the directory name to the execdir argument, like so:

find . -type d -execdir touch {}/foo \;

From man find:

-execdir command {} +

    Like -exec, but the specified command is run from the subdirectory containing the matched file ...

Your matching directory c lives in the b directory, so that is where the exec is run from. It would work as you expect if you are looking for files instead of directories.

You could probably achieve what you want by sending the directories to xargs, as that will be supplied the full list of directories.