Passing multiple directories to the -prune option in find

argumentsarraydirectoryfind

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

Best Answer

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 
./.*/
./Music
./Documents

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