14

When I run find with -execdir I don't get the results I was expecting.

For example:

mkdir -p a/b/c
find . -type d -execdir touch foo \;
$ tree a
a
├── b
│   ├── c
│   └── foo
└── foo

Directory c does not contain a foo file. How do I get find to visit and do something locally in each directory?

14

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'\')
  • FYI: man bash states under "-c": arguments after command_string are assigned to positional parameters starting with $0, however "for d" will iterate through positional parameters starting with $1. "_" is text that is assigned to $0 and will not be used. – Chad Skeeters Jun 29 '16 at 17:54
3

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 \;
2

From man file

   -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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.