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


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

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

Group -path ... -prune into one expression enclosed by \( ... \) using -o (or) logic.

find /somepath \( -path /a -prune -o \
                  -path /b -prune -o \
                  -path /c -prune \
               \) \
               -o -print

The example will not iterate directories or files at or under /somepath/a, /somepath/b, and /somepath/c.

Here is a more specific example using multiple actions.

find / \( -path /dev -prune -o \
          -path /proc -prune -o \
          -path /sys -prune \
       \) \
       -o -printf '%p ' -exec cksum {} \;

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)


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