Exclude directories (and avoid traversing) with find

Last update: 06 January, 2026
Table of contents
$ find . \( -name "node_modules" \
    -or -name ".git" \) -prune \
  -or -type f -print

And this for directories:

$ find . \( -name "node_modules" \
    -or -name ".git" \) -prune \
  -or -type d -print

So, if you want to ignore directories, a good rule of thumb is to put the prune stuff first, and then specify what you want to print. Additionally, don’t forget to put an explicit print at the end. See the questions below for more.

If you have many paths you can use regex:

$ find . \
    -regextype posix-extended \
    -type d \
    -regex ".*\/\.?(node_modules|git|public|cache|next|coverage).*" -prune \
  -or \
    -type d -print
$ find . \
    -regextype posix-extended \
    -type d \
    -regex ".*\/\.?(node_modules|git|public|cache|next|coverage).*" -prune \
  -or \
    -type f -print

Note that you can put more filters after -type and before the ending -print.

Some things to keep in mind

  • The find expressions are evaluated from left to right: “GNU find searches the directory tree rooted at each given starting-point by evaluating the given expression from left to right”
  • If we have 2 expressions separated by -or and the 1st one is true, find won’t evaluate the 2nd expression.
  • If you don’t specify a command, find appends a -print at the end.
  • -prune all it does is to skip traversing the directory tree.
  • An expression that contains -prune at the end, returns true if it prunes and false if it doesn’t prune.

Question 1: Why find . \( -name "node_modules" -or -name ".git" \) -prune still prints the node_modules and .git top-level directories?

$ find . \( -name "node_modules" \
  -or -name ".git" \) -prune

The man page of find states about -prune that “if the file is a directory, do not descend into it”, that’s what it does, nothing more. Also, If the only action is -prune, find still appends a -print that will print the pruned directory name (and avoid descending further into it off course, as a side-effect).

So, if you don’t want to print the pruned top-level directory, append at the end -or expression -print where expression is what you want to print. This way find won’t automatically append a print at the end.

Question 2: Why find . -type f -print -o \( -name node_modules -o -name .git \) -prune gives the same output as the “canonical form” find . \( -name node_modules -o -name .git \) -prune -o -type f -print?

$ find . -type f -print -o \
  \( -name node_modules -o -name .git \) -prune

$ find . \( -name node_modules -o -name .git \) -prune \
  -o -type f -print

The above commands give the same result and it might seem strange at first.

The 2nd expression, aka the “canonical form” (find . \( -name node_modules -o -name .git \) -prune -o -type f -print), behaves the way it does, because, if the item is a regular file it will skip the 1st part, it will go to the 2nd and will print it (for the case when it’s a file and it’s inside a prune directory, keep reading, I explain it further down). If the item is a directory and it’s on the prune directories, it will stop descending further. If it’s a directory and it’s not on the prune directories, it will do nothing.

Now, I want to focus on the 1st expression, and explain how it works.

This question can also take the following form: Why the 1st part of the 1st expression, -type f -print -o doesn’t first print all the files, even those inside the pruned directories? The reason is probably because the find expression is evaluated on each file, and the files are sorted lexicographically ascending by name. Say, for example, you have an index.js file, that starts from i, and a node_modules folder, that starts from n, with ton of sub-folders and files. One of those enclosed files might be a node_modules/some-package/index.js file, which is after node_modules in order. i is before n in the sorting order, so the index.js file will be evaluated first. It’s file, so , because of the 1st part, it’s printed. We then proceed with the top level folder node_modules, that’s not a file, but a directory, so we don’t print it. It matches the 2nd part of the find expression, and it also has a prune command, so we won’t descend into it’s subdirectories and files. It doesn’t have a print command, we won’t print node_modules.

So that’s why we won’t print node_modules/some-package/index.js, we first run the expression on the parent folder node_modules that’s pruned and we don’t descend further down.

Question 3: Why find . -type d -print -o \( -name node_modules -o -name .git \) -prune output is not the same as find . \( -name node_modules -o -name .git \) -prune -o -type d -print? The 2 corresponding commands were equivalent for files (-type f).

$ find . -type d -print -o \
  \( -name node_modules -o -name .git \) -prune

$ find . \( -name node_modules -o -name .git \) -prune \
  -o -type d -print

Let’s start by stating that both of the above commands won’t print any files, they will print only directories, so we don’t have to worry about that.

2nd expression

In the 2nd expression, the “canonical form” (find . \( -name node_modules -o -name .git \) -prune -o -type d -print), in the 1st part of the expression, if the directory is in the prune list, find won’t further traverse it. Also, -prune returns true in this case, so we won’t evaluate the 2nd part of the expression (short-circuits due to -or). We didn’t have to make a note about the fact that -prune returns true in the corresponding case in Q2, about the files, because it was self-evident. We were printing files, not directories, so even if -prune returned false (which is not the case) and the 2nd part of the expression was evaluated, we would not have printed the directory anyway.

If the directory is not in the prune list, the 1st part of the expression (the -prune) returns false, and, because we have an -or, we also evaluate the 2nd part of the expression, and we print the directory.

1st expression

In the 1st expression (find . -type d -print -o \( -name node_modules -o -name .git \) -prune), if the directory is in the prune list, the 1st part will print it (incorrectly), it will return true and it won’t evaluate the 2nd part.

If the directory is not in the prune list, again, the 1st part of the expression will print it (correctly this time), it will return true and it won’t evaluate the 2nd part.

So, the 1st expression just prints all the directories and doesn’t prune anything. You can test that it prints all the directories with the following command: diff <(find . -type d -print -o \( -name node_modules -o -name .git \) -prune) <(find . -type d), the diff will be empty, so the 2 commands are the same.

Question 4: Why use -prune and not -not -path "pathname"

The man page of find suggests to use -prune if you want to exclude directories. If you use something like -not -path "pathname", you will exclude the directory, but you will still traverse it, and, as a result, the search will be slow for big directories like node_modules.

Other things to read

Popular

Previous/Next