I have two folders. I want to find files with a specific extension recursively in dir1 and delete files which are found in dir1 from dir2. I think this can be done with -exec command in find. Deleting it from dir1 is quite simple - but how can this be done for dir2?

find /home/admin/.cache/yay/ -name '*.pkg.tar.zst' -exec basename {} ; (now remove the found files in /tmp/test/)

  • dir1 has subdirectories. Problem is that -exec gets full path to file and I want to delete files with the same name in different folder... The files in dir2 are not in subdirectories of dir2
    – DF8OE
    Oct 28, 2023 at 8:19
  • Please remember to accept the answer that works best for you. Since you've never accepted any answer to your questions you may want to look back and accept those too. You do this with the tick mark ✓ alongside the answer. This is how you say "thankyou" here Oct 28, 2023 at 8:52

2 Answers 2


Seems easier with xargs:

(cd dir1 && find . -name '*.ext' ! -type d -print0) |
  (cd dir2 && xargs -r0 rm -f --)

For each foo.ext and subdir/bar.ext in dir1 would delete the same foo.ext and subdir/bar.ext in dir2.

If you want to delete the files by the same name even if they're not found in the same directories in dir1 and dir2 (for instance in the example above, remove all of dir2/bar.ext, dir2/any/level/of/subdirectory/foo.ext) with zsh:

typeset -U names
names=( dir1/**/*.ext(ND^/:t) )
rm -f -- dir2/**/(${(~j[|])names})(D^/)
  • typeset -U names gives the unique attribute to the variable, so that when assigned as an array, the elements are unique (deduplicated).
  • **/ any level (including 0) of subdirectories
  • N: Nullglob: does not complain if there's no match. Actually, you migh as will leave it off for the shell to abort if doesn't find any .ext file.
  • D: Dotglob: doesn't skip hidden files.
  • ^/ is like find's ! -type d to exclude the files of type directory (which can't be removed without first deleting their contents).
  • :t: gets the tail, the base name
  • ${(j[string])array} joins the elements of the array with string in-between. With ~, the joining string is treated as a glob operator, so we end up with a (foo.ext|bar.ext|...) glob.

Same golfed on one command using an anonymous function (and the u parameter expansion flag in place of typeset -U):

(){rm -f dir2/**/(${(~j[|])${(u)@}})(D^/)} dir1/**/*.ext(D^/:t)

Another approach that would be more efficient if there's a very large number of different names is to use an associative array:

typeset -A to_remove
: dir1/**/*.ext(ND^/e['to_remove[$REPLY:t]=1'])
rm -f -- dir2/**/*.ext(De['(( $to_remove[$REPLY:t] ))']^/)

If you just want to delete them at the root of dir2 (in the example above, remove dir2/foo.ext and dir2/bar.ext only, not those in subdirectories), remove the **/ in the rm commands above or with GNU find and sort:

(cd dir1 && find . -name '*.ext' -printf '%f\0') |
  LC_ALL=C sort -zu |
  (cd dir2 && xargs -r0 rm -f --)

Find the file paths from dir1. Strip back the path to just the filename. Delete each found file from dir2:

find /home/admin/.cache/yay/ -type f -name '*.pkg.tar.zst' -exec sh -c 'for f in "$@"; do echo rm -f -- "/path/to/dir2/${f##*/}"; done' _ {} +

If you don't want the shell loop,

find /home/admin/.cache/yay/ -type f -name '*.pkg.tar.zst' -exec sh -c 'echo rm -f -- "/path/to/dir2/${1##*/}"' _ {} \;

Remove echo when you're satisfied it's going to work as you expect

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .