27

Why isn't this regex working?

  find ./ -regex '.*\(m\|h\)$

I noticed that the following works fine:

  find ./ -regex '.*\(m\)$'

But when I add the "or a h at the end of the filename" by adding \|h it doesn't work. That is, it should pick up all my *.m and *.h files, but I am getting nothing back.

I am on Mac OS X.

mc_kaiser
  • 717
  • 8
  • 18
Greg
  • 34,042
  • 79
  • 253
  • 454

4 Answers4

41

On Mac OS X, you can't use \| in a basic regular expression, which is what find uses by default.

re_format man page

[basic] regular expressions differ in several respects. | is an ordinary character and there is no equivalent for its functionality.

The easiest fix in this case is to change \(m\|h\) to [mh], e.g.

find ./ -regex '.*[mh]$'

Or you could add the -E option to tell find to use extended regular expressions instead.

find -E ./ -regex '.*(m|h)$'

Unfortunately -E isn't portable.

Also note that if you only want to list files ending in .m or .h, you have to escape the dot, e.g.

find ./ -regex '.*\.[mh]$'

If you find this confusing (me too), there's a great reference table that shows which features are supported on which systems.

Regex Syntax Summary [Google Cache]

Mikel
  • 24,855
  • 8
  • 65
  • 66
  • 1
    Unless `find` is using a different flavor of regex, the original `\(m\|h\)` is matching `(m|h)` literally, not either an "m" or an "h". While you're solving the question, it seems like you're just providing a way around it and you didn't address the *why*. – coreyward May 06 '11 at 00:18
  • No, according the [man page](http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/find.1.html), Mac OS/Darwin/BSD find uses basic regular expressions by default. – Mikel May 06 '11 at 00:20
  • @Mikel Right…so the reason the regex in the question didn't work is because it was escaping the characters. Reading comprehension is inversely correlated to reading speed. ;) – coreyward May 06 '11 at 00:27
  • @coreyward: Yes and no. It's actually trying to match literally `m\|h`. On Mac OS X, `\(` and `\)` are supported, but `\|` isn't. – Mikel May 06 '11 at 00:31
  • Way too complicated! You don’t need a regex. Globs work fine: `find . -name '*.[mh]' -type f` – tchrist May 06 '11 at 00:51
  • 3
    @Mikel: yes, I know this is an old thread. I thought I would mention that the `-E` should come *before* the path variable. E.g., `find -E ./ -regex ".*(m|h)$"` – David Vezzani Nov 21 '13 at 19:42
12

A more efficient solution is to use the -o flag:

find . -type f \( -name "*.m" -o -name "*.h" \)

but if you want the regex use:

find . -type f -regex ".*\.[mh]$"
Mikel
  • 24,855
  • 8
  • 65
  • 66
Wes
  • 6,455
  • 3
  • 22
  • 26
  • `-type f` in the first example was only being applied to `-name "*.h"` (and not `-name "*.m"`) due to precedence. Fixed. – Mikel May 06 '11 at 00:51
  • Why do you need that weird `\( .. \)`. It works if you chain `-name -o -name ...` – pronebird Jul 27 '18 at 09:51
3

Okay this is a little hacky but if you don't want to wrangle the regex limitations of find on OSX, you can just pipe find's output to grep:

find . | grep ".*\(\h\|m\)"
1

What’s wrong with

find . -name '*.[mh]' -type f

If you want fancy patterns, then use find2perl and hack the pattern.

tchrist
  • 78,834
  • 30
  • 123
  • 180