Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am using find to locate and delete backup files but wish to exclude certain directories from the search. The backup filenames could terminate in .bck, bak, ~, or backup.

The Minimal Working Example (MWE) code with only three directories to exclude is:

#! /bin/bash
find . -type d \( -path "./.*" -o -path "./Music" -o -path "./Documents" \) -prune -o -type f \( -name "*.bck" -o -name "*.bak" -o -name "*~" -o -name "*.backup" \) -print0 | xargs -0 --no-run-if-empty trash-put

The syntax \( -path "dir1" -o -path "dir2" ... -o -path "dirN" \) -prune seems a little clunky, especially if there are around ten directories to exclude, although I have shown only three in the MWE.

Is there a more elegant way using either an input file, with the list of excluded directories, or an array- or list-like construct, that could be pressed into service?

I am sorry for not being more explicit when I wrote my original question.

NB: trash-put is a utility that moves the files to the Trashcan instead of deleting them [1].

[1]. https://github.com/andreafrancia/trash-cli

share|improve this question
up vote 2 down vote accepted

As far as I know, there is no option to tell find to read patterns from a file. An easy workaround is to save the patterns I want to exclude in a file and pass that file as input for a reverse grep. As an example, I have created the following files and directories:

$ tree -a
├── a
├── .aa
├── .aa.bak
├── a.bck
├── b
├── .dir1
│   └── bb1.bak
├── dir2
│   └── bb2.bak
├── b.bak
├── c
├── c~
├── Documents
│   └── Documents.bak
├── exclude.txt
├── foo.backup
└── Music
    └── Music.bak

If I understood the example you posted correctly, you want to move a.bck, .aa.bak, b.bak, c~, foo.backup and dir2/bb2.bak to the trash and leave .aa.bak, .dir1/bb1.bak, Documents/Documents.bak and Music/Music.bak where they are. I have, therefore, created the file exclude.txt with the following contents (you can add as many as you want):

$ cat exclude.txt 

I use ./.*/ because I understood your original find to mean that you want to move hidden backup files (.foo) that are in the current directory but exclude any backup files that are in hidden directories (.foo/bar). So, I can now run the find command and use grep to exclude unwanted files:

$ find . -type f | grep -vZf exclude.txt | xargs -0 --no-run-if-empty trash-put

Grep options:

   -v, --invert-match
          Invert  the  sense  of matching, to select non-matching
          lines.  (-v is specified by POSIX.)
   -f FILE, --file=FILE
          Obtain patterns from FILE, one  per  line.   The  empty
          file  contains  zero  patterns,  and  therefore matches
          nothing.  (-f is specified by POSIX.)
   -Z, --null
          Output a zero byte (the ASCII NUL character) instead of
          the  character  that normally follows a file name.  For
          example, grep -lZ outputs a zero byte after  each  file
          name  instead  of the usual newline.  This option makes
          the output unambiguous, even in the  presence  of  file
          names  containing  unusual  characters  like  newlines.
          This  option  can  be  used  with  commands  like  find
          -print0,  perl  -0,  sort  -z,  and xargs -0 to process
          arbitrary file names, even those that  contain  newline
share|improve this answer
I am so sorry for not being explicit. Kindly see revised question which I hope is clearer. – chandra Apr 29 '13 at 14:30
@chandra see updated answer, same general idea, different details. – terdon Apr 29 '13 at 18:01
Thank you. You have answered my question very clearly and perfectly for my purpose. I have accepted your answer. – chandra Apr 30 '13 at 5:06

With GNU find (i.e. under non-embedded Linux or Cygwin), you can use -regex to combine all these -path wildcards into a single regex.

find . -regextype posix-extended \
     -type d -regex '\./(\..*|Music|Documents)' -prune -o \
     -type f -regex '.*(\.(bck|bak|backup)|~)' -print0 |
xargs -0 --no-run-if-empty trash-put

With FreeBSD or OSX, use -E instead of -regextype posix-extended.

share|improve this answer
Thank you for an excellent alternative answer. It is a shame that I cannot accept two answers. – chandra Apr 30 '13 at 5:07

This seems to be more a shell question than a find question. With a file containing ( -name dir1 -o -name dir2 ) -prune (no "\"!) you can simply do this:

find ... $(< /path/to/file)

Without changing the find call itself (to eval find or by changing $IFS) this works with paths without whitespace only, though.

If you want to keep the file simpler you can write a script.

# file content

# script content
# file may be checked for whitespace here
grep '[^[:space:]]' "$file" | { empty=yes
  while read dir; do
    if [ yes = "$empty" ]; then
      echo -n "( "
      echo -n " -o "
    echo -n "-name ${dir}"
  if [ no = "$empty" ]; then
    echo -n " ) -prune"
  fi; }

And use

find ... $(/path/to/script)


share|improve this answer
I am so sorry for not being explicit. Kindly see revised question which I hope is clearer. – chandra Apr 29 '13 at 14:30
@chandra I neither see how your question is clearer nor do I understand what could be a problem with my solution (except for the trivial replecement of -name by path). – Hauke Laging Apr 29 '13 at 16:27
My script above works and does what I want it to. I simply wanted to know whether there was a neater way than \( -path "dir1" -o -path "dir2" ... -o -path "dirN" \) -prune to exclude certain directories from the recursive search that find does. I am not searching for anything within files but rather deleting certain files and avoiding certain directories in my search path. I do not understand what your script is trying to do either. So, it appears we have a miscommunication. Sorry. Let us leave it at that. – chandra Apr 29 '13 at 17:48

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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