How to exclude a directory in find . command in Linux?

Use the -prune primary. For example, if you want to exclude ./misc:

find . -path ./misc -prune -o -name '*.txt' -print

To exclude multiple directories, OR them between parentheses.

find . -type d \( -path ./dir1 -o -path ./dir2 -o -path ./dir3 \) -prune -o -name '*.txt' -print

And, to exclude directories with a specific name at any level, use the -name primary instead of -path.

find . -type d -name node_modules -prune -o -name '*.json' -print

If -prune doesn’t work for you, this will:

find -name "*.js" -not -path "./directory/*"

Caveat: requires traversing all of the unwanted directories.

How to exclude a directory in find . command in Linux?

I find the following easier to reason about than other proposed solutions:

find build -not \( -path build/external -prune \) -name \*.js
# you can also exclude multiple paths
find build -not \( -path build/external -prune \) -not \( -path build/blog -prune \) -name \*.js

Important Note: the paths you type after -path must exactly match what find would print without the exclusion. If this sentence confuses you just make sure to use full paths through out the whole command like this: find /full/path/ -not \( -path /full/path/exclude/this -prune \) .... See note [1] if you’d like a better understanding.

Inside \( and \) is an expression that will match exactly build/external (see important note above), and will, on success, avoid traversing anything below. This is then grouped as a single expression with the escaped parenthesis, and prefixed with -not which will make find skip anything that was matched by that expression.

One might ask if adding -not will not make all other files hidden by -prune reappear, and the answer is no. The way -prune works is that anything that, once it is reached, the files below that directory are permanently ignored.

This comes from an actual use case, where I needed to call yui-compressor on some files generated by wintersmith, but leave out other files that need to be sent as-is.


Note [1]: If you want to exclude /tmp/foo/bar and you run find like this “find /tmp \(...” then you must specify -path /tmp/foo/bar. If on the other hand you run find like this cd /tmp; find . \(... then you must specify -path ./foo/bar.

Answer #3:

There is clearly some confusion here as to what the preferred syntax for skipping a directory should be.

GNU Opinion

To ignore a directory and the files under it, use -prune

From the GNU find man page.

Reasoning

-prune stops find from descending into a directory. Just specifying -not -path will still descend into the skipped directory, but -not -path will be false whenever find tests each file.

Issues with -prune

-prune does what it’s intended to, but are still some things you have to take care of when using it.

  1. find prints the pruned directory.
    • TRUE That’s intended behavior, it just doesn’t descend into it. To avoid printing the directory altogether, use a syntax that logically omits it.
  2. -prune only works with -print and no other actions.
    • NOT TRUE-prune works with any action except -deleteWhy doesn’t it work with delete? For -delete to work, find needs to traverse the directory in DFS order, since -deletewill first delete the leaves, then the parents of the leaves, etc… But for specifying -prune to make sense, find needs to hit a directory and stop descending it, which clearly makes no sense with -depth or -delete on.

Performance

I set up a simple test of the three top upvoted answers on this question (replaced -print with -exec bash -c 'echo $0' {} \; to show another action example). Results are below

----------------------------------------------
# of files/dirs in level one directories
.performance_test/prune_me     702702    
.performance_test/other        2         
----------------------------------------------

> find ".performance_test" -path ".performance_test/prune_me" -prune -o -exec bash -c 'echo "$0"' {} \;
.performance_test
.performance_test/other
.performance_test/other/foo
  [# of files] 3 [Runtime(ns)] 23513814

> find ".performance_test" -not \( -path ".performance_test/prune_me" -prune \) -exec bash -c 'echo "$0"' {} \;
.performance_test
.performance_test/other
.performance_test/other/foo
  [# of files] 3 [Runtime(ns)] 10670141

> find ".performance_test" -not -path ".performance_test/prune_me*" -exec bash -c 'echo "$0"' {} \;
.performance_test
.performance_test/other
.performance_test/other/foo
  [# of files] 3 [Runtime(ns)] 864843145

Conclusion

 The syntax in the second answer performed the better of the two -prune syntaxes; but, I strongly suspect this is the result of some caching as switching the order in which the two ran resulted in the opposite result, while the non-prune version was always the slowest.

Answer #4:

This is the only one that worked for me.

find / -name MyFile ! -path '*/Directory/*'

Searching for “MyFile” excluding “Directory”. Give emphasis to the stars * .

Answer #5:

Tested in Linux Ubuntu 18.04 and 20.04.

Notice that the ./ (or */, see below) before and the /* (or *, but see the caveat below) after the folder name to exclude are required in order to exclude dir_to_exclude, and anything within it!

Also, for speed, and to not traverse excluded directories, notice the really important escaped grouping parenthesis and the -prune option. Ex: find -not \( -path "*/dir_to_exclude/*" -prune \).

To see examples of these escaped grouping parenthesis in the manual pages, run man find, and then press / to search. Search for the pattern \(, for instance, using the regular expression pattern \\\(. Press Enter to begin searching the man pages. Press N for “next match” while searching.

Summary

These work:

# [my favorite #1] exclude contents of `dir_to_exclude` at the search root
find -not -path "./dir_to_exclude/*"

# exclude all files & folders beginning with the name `dir_to_exclude` at the
# search root   
find -not -path "./dir_to_exclude*"

# [my favorite #2] exclude contents of `dir_to_exclude` at any level within your
# search path
find -not -path "*/dir_to_exclude/*"

# exclude all files & folders beginning with the name `dir_to_exclude` at any
# level within your search path
find -not -path "*/dir_to_exclude*"

# To exclude multiple matching patterns, use `-not -path "*/matching pattern/*"`
# multiple times, like this
find -not -path "*/dir_to_exclude1/*" -not -path "*/dir_to_exclude2/*"

[USE THESE] These work too, and are BETTER because they cause find to NOT unnecessarily traverse down excluded paths!:
(This makes a huge difference in speed (is 2x~100x faster)! You can also search the man find pages locally for the strings \( and \) with the escaped search strings \\\( and \\\), respectively).

find -not \( -path "./dir_to_exclude" -prune \)  # works here but not above
find -not \( -path "./dir_to_exclude*" -prune \)
find -not \( -path "./dir_to_exclude/*" -prune \)
find -not \( -path "*/dir_to_exclude" -prune \)  # works here but not above
find -not \( -path "*/dir_to_exclude*" -prune \)
find -not \( -path "*/dir_to_exclude/*" -prune \)

# To exclude multiple matching patterns at once, use the `-not \( ... \)` 
# pattern multiple times, like this
find -not \( -path "*/dir_to_exclude1/*" -prune \) \
     -not \( -path "*/dir_to_exclude2/*" -prune \)

…but these do NOT work:

# These do NOT work!
find -not -path "dir_to_exclude"
find -not -path "dir_to_exclude/*"
find -not -path "./dir_to_exclude"
find -not -path "./dir_to_exclude/"

The key is that generally, to make it work, you must begin each matching pattern with either ./ or */, and end each matching pattern with either /* or *, depending on what you’re trying to achieve. I say “generally”, because there are two noted exceptions in the -not \( ... \)-style section above. You can identify these two exceptions by the comments to the right of them which say: # works here but not above.

Further Explanation:

  1. [BEST, depending on what you want] This WORKS! Exclude all files and folders inside dir_to_exclude at the root of where you are searching. Note that this excludes all subfiles and subfolders inside dir_to_exclude, but it does NOT exclude the dir_to_exclude dir itself.
    find -not \( -path "./dir_to_exclude/*" -prune \)
  2. Also exclude the dir_to_exclude dir itself (and any file or folder with a name which begins with these characters). Caveat: this also excludes dir_to_exclude1dir_to_exclude2dir_to_exclude_anyTextHere, etc. It excludes ANY file or folder which merely begins with the text dir_to_exclude and is in the root directory of where you’re searching.
    find -not \( -path "./dir_to_exclude*" -prune \)
  3. [BEST, depending on what you want] to recursively exclude a dir by this name at any level in your search path. Simply add a wildcard * to the front of the path too, rather than using the . to indicate the search root directory.
    find -not \( -path "*/dir_to_exclude/*" -prune \)
  4. Recursively exclude any file or folder with a name that begins with the characters dir_to_exclude at any level in your search path. (See also the caveat above).
    find -not \( -path "*/dir_to_exclude*" -prune \)

Summary:

In ./, the . at the beginning means “start in the current directory” (or in */, the * is a wildcard to pick up any characters up to this point), and in /* at the end, the * is a wildcard to pick up any characters in the path string after the / character. That means the following:

  1. "./dir_to_exclude/*" matches all subfiles and subfolders within dir_to_exclude in the root search directory (./), but does NOT match the directory itself.
  2. "./dir_to_exclude*" matches all files and folders within the root search directory (./), including dir_to_exclude, as well as all contents within it, but also with the caveat it will match any file or folder name beginning with the characters dir_to_exclude.
  3. "*/dir_to_exclude/*" matches all subfiles and subfolders within dir_to_exclude in any directory at any level in your search path (*/), but does NOT match the directory itself.
  4. "*/dir_to_exclude*" matches all files and folders at any level (*/) within your search path with a name which begins with dir_to_exclude.

Going further

From there, I like to pipe to grep to search for certain matching patterns in the paths of interest. Ex: search for any path that is NOT inside the dir_to_exclude directory, and which has desired_file_name.txt in it:

# Case-sensitive; notice I use `\.` instead of `.` when grepping, in order to
# search for the literal period (`.`) instead of the regular expression
# wildcard char, which is also a period (`.`).
find -not \( -path "./dir_to_exclude/*" -prune \) \
    | grep "desired_file_name\.txt"

# Case-INsensitive (use `-i` with your `grep` search)
find -not \( -path "./dir_to_exclude/*" -prune \) \
    | grep -i "desired_file_name\.txt"

# To make `dir_to_exclude` also case INsensitive, use the `find` `-ipath` option
# instead of `-path`:
find -not -ipath \( -path "./dir_to_exclude/*" -prune \) \
    | grep -i "desired_file_name\.txt"

To exclude multiple matching patterns, simply use -not \( -path "*/matching pattern/*" -prune \) multiple times. Ex:

# Exclude all ".git" and "..git" dirs at any level in your search path
find -not \( -path "*/.git/*" -prune \) -not \( -path "*/..git/*" -prune \)

I use the above example as part of my sublf alias here (update: that alias is being expanded and moved into a sublf.sh script in this folder here instead). This alias allows me to use the fzf fuzzy finder to quickly search for and open multiple files in Sublime Text. See the links above for the latest version of it.

alias sublf='FILES_SELECTED="$(find -not \( -path "*/.git/*" -prune \) \
-not \( -path "*/..git/*" -prune \) \
| fzf -m)" \
&& echo "Opening these files in Sublime Text:" \
&& echo "$FILES_SELECTED" \
&& subl $(echo "$FILES_SELECTED")'

Answer #6:

-prune definitely works and is the best answer because it prevents descending into the dir that you want to exclude. -not -path which still searches the excluded dir, it just doesn’t print the result, which could be an issue if the excluded dir is mounted network volume or you don’t permissions.

The tricky part is that find is very particular about the order of the arguments, so if you don’t get them just right, your command may not work. The order of arguments is generally as such:

find {path} {options} {action}

{path}: Put all the path related arguments first, like . -path './dir1' -prune -o

{options}: I have the most success when putting -name, -iname, etc as the last option in this group. E.g. -type f -iname '*.js'

{action}: You’ll want to add -print when using -prune

Here’s a working example:

# setup test
mkdir dir1 dir2 dir3
touch dir1/file.txt; touch dir1/file.js
touch dir2/file.txt; touch dir2/file.js
touch dir3/file.txt; touch dir3/file.js

# search for *.js, exclude dir1
find . -path './dir1' -prune -o -type f -iname '*.js' -print

# search for *.js, exclude dir1 and dir2
find . \( -path './dir1' -o -path './dir2' \) -prune -o -type f -iname '*.js' -print

How to exclude a directory in find . command? Answer #7:

There are plenty of good answers, it just took me some time to understand what each element of the command was for and the logic behind it.

find . -path ./misc -prune -o -name '*.txt' -print

find will start finding files and directories in the current directory, hence the find ..

The -o option stands for a logical OR and separates the two parts of the command :

[ -path ./misc -prune ] OR [ -name '*.txt' -print ]

Any directory or file that is not the ./misc directory will not pass the first test -path ./misc. But they will be tested against the second expression. If their name corresponds to the pattern *.txt they get printed, because of the -print option.

When find reaches the ./misc directory, this directory only satisfies the first expression. So the -prune option will be applied to it. It tells the find command to not explore that directory. So any file or directory in ./misc will not even be explored by find, will not be tested against the second part of the expression and will not be printed.

About ᴾᴿᴼᵍʳᵃᵐᵐᵉʳ

Linux and Python enthusiast, in love with open source since 2014, Writer at programming-articles.com, India.

View all posts by ᴾᴿᴼᵍʳᵃᵐᵐᵉʳ →