find . -name '*.txt' and find . -regex '.*\.txt' look like they do the same thing, and most of the time they return the same files. They are not the same tool. -name takes a shell glob and matches the basename. -regex takes a full regular expression and matches the whole path. That whole-path detail is the single biggest surprise: find . -regex '.txt' matches nothing, because the pattern has to match the entire path string, not just the part you care about.
This page is the reference I keep open when a -name expression is turning into a pile of -o clauses and it is time to switch to -regex. It covers the glob-versus-regex split, the -regextype flavors, the GNU-versus-BSD default-flavor drift that quietly breaks scripts moved between machines, and the cases where -name is still the better choice.
Set your values
Set your OS and search path. Every find example below updates with your values.
-name uses shell globs
-name matches each file's basename against a shell glob pattern. Glob is the small wildcard language the shell itself uses for filename expansion:
| Glob | Matches |
|---|---|
* | Any run of characters (including none) |
? | Exactly one character |
[abc] | One character from the set |
[a-z] | One character from the range |
[!abc] | One character not in the set |
So -name '*.txt' reads as "basename ends with .txt". A few things follow from "glob, basename":
- The pattern is matched against the basename only.
find . -name 'src/*.ts'never matches, because a basename never contains a slash. *in a glob is "any characters". It is not the regex*(which means "zero or more of the previous token"). Different language, same symbol.- Always quote the pattern. Unquoted
-name *.txtlets the shell expand the glob beforefindever sees it.
find :search_path -name '*.txt'-iname is the case-insensitive variant, matching .txt, .TXT, and .Txt alike.
-regex uses a full regular expression
-regex matches each file against a regular expression, and here is the part that trips everyone up: the regex must match the entire path string that find is currently considering, not the basename, and not a substring.
That means a pattern like .txt fails. find is testing paths such as ./notes/todo.txt, and the regex .txt only matches a three-character string. The pattern has to describe the whole thing:
# WRONG: matches nothing, the regex must match the whole path
find . -regex '.txt'
# RIGHT: .* soaks up the leading directory components
find . -regex '.*\.txt'.* at the front is doing real work. It matches "any leading path", so .*\.txt reads as "any path that ends in a literal .txt". The \. is an escaped dot (a literal period); a bare . in a regex means "any character".
find :search_path -regextype posix-extended -regex '.*\.txt'-iregex is the case-insensitive variant of -regex, exactly as -iname is to -name.
Note the PowerShell column. Where-Object { $_.FullName -match '...' } is the closest Windows analogue: PowerShell's -match operator runs a .NET regular expression, and unlike find -regex, it matches a substring by default, so '\.txt$' (anchored at the end) is the natural way to express "ends with .txt".
-regextype: choosing the regex flavor (GNU only)
There is no single "regular expression" syntax. There are flavors, and they disagree about things like whether ( is a literal paren or a group, and how + behaves. GNU find lets you pick the flavor with -regextype, which must appear before the -regex test on the command line:
-regextype value | Flavor | Notes |
|---|---|---|
emacs | Emacs regex | The GNU find default if you never pass -regextype |
posix-basic | POSIX BRE | ( and + are literals; groups need \( \) |
posix-extended | POSIX ERE | ( groups, + and ? work unescaped; what most people want |
posix-awk | awk regex | ERE-like, with awk's escape rules |
posix-egrep | egrep regex | ERE-like, matches old egrep behavior |
In practice I pass -regextype posix-extended almost every time I use -regex on Linux. ERE is the flavor that behaves the way a regex written in 2026 expects: (jpe?g|png) works without backslash-escaping every grouping construct.
# emacs default: groups still work, but escape rules differ from ERE
find . -regex '.*\.\(jpe?g\|png\)'
# posix-extended: unescaped groups and alternation, the modern default
find . -regextype posix-extended -regex '.*\.(jpe?g|png)'BSD find has no -regextype
This is the cross-platform footgun. macOS ships BSD find, and BSD find has no -regextype flag at all. It supports -regex and -iregex, but the flavor is fixed.
Worse, the default flavor differs between the two implementations:
| Behavior | GNU find (Linux) | BSD find (macOS) |
|---|---|---|
-regex test | Supported | Supported |
-iregex (case-insensitive) | Supported | Supported |
-regextype flag | Supported | Not supported, errors out |
| Default regex flavor | Emacs regex | Basic POSIX (BRE) |
| Match target | Whole path | Whole path |
So a script written and tested on Linux with find . -regextype posix-extended -regex '.*\.(jpg|png)' fails outright on a Mac: BSD find does not recognize -regextype and exits with an error. Strip the -regextype and the alternation (jpg|png) then needs \(jpg\|png\) because BSD's default is basic POSIX, where the parens are literals unless escaped.
The portable approaches, in order of how much I trust them:
- Install GNU findutils on macOS (
brew install findutils) and callgfindexplicitly. Now both machines run the same implementation. - Avoid
-regexin cross-platform scripts. Use-namewith a few-oclauses, which behaves identically everywhere. - Branch on
unameand keep two versions of the expression. Workable, but the version drift is exactly the bug you were trying to avoid.
Worked example: match three extensions
Here is the case that actually pushes me from -name to -regex. I want every JPEG, JPG-spelled-jpeg, and PNG image under a tree. With -name it is three clauses joined by -o (logical OR), and the whole group needs parentheses so the OR does not swallow later tests:
find :search_path -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \)With -regex it is one alternation. The i in -iregex makes it case-insensitive so .JPG matches too:
find :search_path -type f -regextype posix-extended -iregex '.*\.(jpe?g|png)'The regex form is shorter and reads as a single intent: "files ending in jpg, jpeg, or png". jpe?g collapses jpg and jpeg into one token (the ? makes the e optional). Note the macOS variant drops -regextype posix-extended: BSD find rejects that flag. Whether BSD's default flavor accepts the unescaped (jpe?g|png) depends on the macOS version, which is exactly the kind of uncertainty that makes me reach for the -name form in any script that has to run on both.
For the dedicated extension-matching reference, including the glob-only patterns, see the find files by extension deep dive.
Common find -regex mistakes
1. Forgetting that -regex matches the whole path. find . -regex 'todo.txt' matches nothing. find evaluates paths like ./notes/todo.txt, and the pattern has to describe that entire string. Prefix with .* or anchor against the real path shape.
2. Forgetting the leading .*. Even find . -regex 'todo\.txt' fails for the same reason. Use .*todo\.txt ("any path ending in todo.txt") or .*/todo\.txt if you specifically want it as a basename.
3. Using a regex * where you meant a glob *. In a regex, * means "zero or more of the previous token". A bare -regex '*.txt' is a malformed regex (nothing precedes the *). The "any characters" wildcard in regex is .*.
4. Forgetting to escape the dot. A bare . matches any character, so .*.txt also matches mytxt, aXtxt, and 9_txt. Write .*\.txt so the dot before the extension is literal.
5. Passing -regextype to BSD find. On macOS this is a hard error, not a warning. There is no -regextype on BSD. Either install GNU findutils or drop the flag and accept BSD's fixed flavor.
6. Assuming the default flavor is the same everywhere. GNU defaults to Emacs regex, BSD defaults to basic POSIX. A pattern with unescaped (...) alternation can work on one and fail on the other. Always pass -regextype posix-extended on GNU, and test on the real target platform.
7. Putting -regextype after -regex. GNU find reads the command line left to right; -regextype only affects -regex tests that come after it. Put it before the -regex it should govern.
When NOT to use -regex
-regex is the right tool for alternation, character classes, and full-path matching. For everything else, -name is faster to type, easier to read, and behaves identically on every platform. Reach for -name (or -iname) when:
- You are matching a single extension or prefix.
-name '*.log'is clearer than-regex '.*\.log'and there is no whole-path gotcha to remember. - The script has to run on both Linux and macOS.
-namehas no flavor and no-regextypedependency.-regexcarries the GNU-versus-BSD drift with it. - You only care about the basename.
-namematches the basename for free. With-regexyou have to model the leading directories with.*or.*/yourself. - The pattern is simple enough for one or two
-oclauses. Two extensions joined by-ois fine. It is when the list hits three or more, or you need a character class, that-regexstarts to pay for itself.
-regex earns its place when the matching logic genuinely needs a regular expression: alternation across several patterns, a character class like [0-9], anchoring against directory structure, or "this set of extensions but only under that subtree". Below that bar, -name is the better default.
See also
- find Command Cheat Sheet: the full find reference covering name, type, size, time, perms, and -exec
- Find files by extension: the dedicated extension-matching reference, glob and regex forms
- find BSD vs GNU on macOS and Linux: every BSD-versus-GNU divergence, not just regex
- grep cheat sheet: the same flavor questions (BRE, ERE, PCRE) show up in grep too
- External: GNU findutils manual, FreeBSD find(1) man page.





