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.
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.
-prune
only works with-print
and no other actions.- NOT TRUE.
-prune
works with any action except-delete
. Why doesn’t it work with delete? For-delete
to work, find needs to traverse the directory in DFS order, since-delete
will 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.
- NOT TRUE.
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:
- [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 insidedir_to_exclude
, but it does NOT exclude thedir_to_exclude
dir itself.find -not \( -path "./dir_to_exclude/*" -prune \)
- Also exclude the
dir_to_exclude
dir itself (and any file or folder with a name which begins with these characters). Caveat: this also excludesdir_to_exclude1
,dir_to_exclude2
,dir_to_exclude_anyTextHere
, etc. It excludes ANY file or folder which merely begins with the textdir_to_exclude
and is in the root directory of where you’re searching.find -not \( -path "./dir_to_exclude*" -prune \)
- [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 \)
- 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:
"./dir_to_exclude/*"
matches all subfiles and subfolders withindir_to_exclude
in the root search directory (./
), but does NOT match the directory itself."./dir_to_exclude*"
matches all files and folders within the root search directory (./
), includingdir_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 charactersdir_to_exclude
."*/dir_to_exclude/*"
matches all subfiles and subfolders withindir_to_exclude
in any directory at any level in your search path (*/
), but does NOT match the directory itself."*/dir_to_exclude*"
matches all files and folders at any level (*/
) within your search path with a name which begins withdir_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.