425

I was trying to get a list of all python and html files in a directory with the command find Documents -name "*.{py,html}".

Then along came the man page:

Braces within the pattern (‘{}’) are not considered to be special (that is, find . -name 'foo{1,2}' matches a file named foo{1,2}, not the files foo1 and foo2.

As this is part of a pipe-chain, I'd like to be able to specify which extensions it matches at runtime (no hardcoding). If find just can't do it, a perl one-liner (or similar) would be fine.

Edit: The answer I eventually came up with include all sorts of crap, and is a bit long as well, so I posted it as an answer to the original itch I was trying to scratch. Feel free to hack that up if you have better solutions.

oguz ismail
  • 1
  • 16
  • 47
  • 69
Xiong Chiamiov
  • 13,076
  • 9
  • 63
  • 101
  • Related: [How to use find command to search for multiple extensions](http://unix.stackexchange.com/q/15308/21471) at Unix SE – kenorb Apr 13 '15 at 13:46
  • 1
    An often overlooked and underused utility is also `locate`, albeit with the caveat that the internal updatedb may not be up-to-date. But it's fast. – michael Mar 12 '18 at 10:01
  • 1
    I'm voting to close this question as off-topic because it belongs on Unix&Linux – Dan Dascalescu Apr 11 '20 at 22:39

12 Answers12

607

Use -o, which means "or":

find Documents \( -name "*.py" -o -name "*.html" \)

You'd need to build that command line programmatically, which isn't that easy.

Are you using bash (or Cygwin on Windows)? If you are, you should be able to do this:

ls **/*.py **/*.html

which might be easier to build programmatically.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
RichieHindle
  • 272,464
  • 47
  • 358
  • 399
  • 3
    I'm using zsh, which, as a general rule, supports all bashisms, plus more. – Xiong Chiamiov Jul 15 '09 at 20:50
  • 18
    Zsh supports `**` for recursive search; Bash only supports it in versions 4.0 and up, and only with `shopt -s globstar`. – ephemient Jul 16 '09 at 01:59
  • 2
    How many -o args can you have? I've got a potentially large list of .gcda (coverage data) files to build up – Jasper Blues Jan 04 '14 at 08:01
  • 49
    You need to surround the two `-name`s with brackets, if you're using `-exec`. E.g. `find Documents \( -name "*.py" -o -name "*.html" \) -exec file {} \;` – artbristol Mar 10 '14 at 14:23
  • 3
    @artbristol comment is very relevant if, for example, you are adding a `-print0` to handle filenames with spaces. – nimrodm Apr 28 '15 at 11:51
  • When doing this, let's say you wanted a type and maxdepth. Could you first do `-maxdepth 1 -type d ` a single time? Or must that happen during each `-o` case? – JacobIRR Aug 04 '17 at 18:52
  • server/path1/path2 -name '*.log' -o -name "*.sta" > destination/results.txt – Jewenile Sep 05 '17 at 11:54
  • 1
    TIL ** will match any dir in shell globbing (but not multiple dir levels, you'd have to specify **/**/**/*.txt to go that deep) – austinheiman Oct 02 '17 at 13:39
  • @austinheiman, also, `**/*.py` won't match any `.py` files in the *current* directory. – Dan Dascalescu Apr 11 '20 at 22:41
  • Bash syntax never ceases to amaze me in how poorly designed it is – Andy Ray Dec 22 '22 at 02:29
90

Some editions of find, mostly on linux systems, possibly on others aswell support -regex and -regextype options, which finds files with names matching the regex.

for example

find . -regextype posix-egrep -regex ".*\.(py|html)$" 

should do the trick in the above example. However this is not a standard POSIX find function and is implementation dependent.

simbabque
  • 53,749
  • 8
  • 73
  • 136
intelekt
  • 1,129
  • 8
  • 6
  • 24
    Simpler: `find . -regex ".*\.\(py\|html\)$"` this works because find defaults to Emacs-style regular expressions, which are slightly different, so you don't have to specify the regextype. – robru Apr 24 '13 at 18:41
  • 4
    If you have many expressions `-regextype posix-egrep` is handy (otherwise you'd need to escape lots of characters). This is the find command I've used for a dist-hook building a Windows distribution zip (finds the files to change and in-file changes them to dos-eol): `find -regextype posix-egrep -regex ".*(\.([chyl]|def|cpy|cob|conf|cfg)|(README|ChangeLog|AUTHORS|ABOUT-NLS|NEWS|THANKS|TODO|COPYING.*))$" -exec sed -i -e 's/\r*$/\r/' {} \;` – Simon Sobisch Aug 04 '16 at 08:58
46

This will find all .c or .cpp files on linux

$ find . -name "*.c" -o -name "*.cpp"

You don't need the escaped parenthesis unless you are doing some additional mods. Here from the man page they are saying if the pattern matches, print it. Perhaps they are trying to control printing. In this case the -print acts as a conditional and becomes an "AND'd" conditional. It will prevent any .c files from being printed.

$ find .  -name "*.c" -o -name "*.cpp"  -print

But if you do like the original answer you can control the printing. This will find all .c files as well.

$ find . \( -name "*.c" -o -name "*.cpp" \) -print

One last example for all c/c++ source files

$ find . \( -name "*.c" -o -name "*.cpp"  -o -name "*.h" -o -name "*.hpp" \) -print
netskink
  • 4,033
  • 2
  • 34
  • 46
39

You could programmatically add more -name clauses, separated by -or:

find Documents \( -name "*.py" -or -name "*.html" \)

Or, go for a simple loop instead:

for F in Documents/*.{py,html}; do ...something with each '$F'... ; done
Brendan Long
  • 53,280
  • 21
  • 146
  • 188
Stephan202
  • 59,965
  • 13
  • 127
  • 133
13

I had a similar need. This worked for me:

find ../../ \( -iname 'tmp' -o -iname 'vendor' \) -prune -o \( -iname '*.*rb' -o -iname '*.rjs' \) -print
bkidd
  • 560
  • 7
  • 7
  • I wanted to find files that matched *.c *.cpp or *.cc With only two -name patterns I didn't need parens but with three -name patterns joined with two -o patterns `find -name "*.cpp" -o -name "*.c" -o -name "*.cc" -print0` I had to use a pair of parens to group the second or operator. `find -name "*.cpp" -o \( -name "*.c" -o -name "*.cc" \) -print0` It may be that the -print0, which is always "true" affected the logic. – cardiff space man Jul 19 '16 at 22:00
4

My default has been:

find -type f | egrep -i "*.java|*.css|*.cs|*.sql"

Like the less process intencive find execution by Brendan Long and Stephan202 et al.:

find Documents \( -name "*.py" -or -name "*.html" \)

PaSe
  • 93
  • 1
  • 1
  • 4
    that's not a correct use of `egrep` regexp, rather, you've a shell glob where a regexp should be used. (Also, typical `find` usage is: `find {directory} [options...] [action]`, where, depending on impl, `directory` may default to `.`, and `action` defaults to `-print`, but I'll be explicit.) So, instead, use something like: `find . -type f -print | egrep -i '\.java$|\.css$|\.cs$|\.sql$'` But also, as really fast alternative to `find` , one might also try `locate` in a similar fashion (albeit not necessarily up to date, since it queries an internal db for a file list) – michael Mar 12 '18 at 09:57
3

Braces within the pattern \(\) is required for name pattern with or

find Documents -type f \( -name "*.py" -or -name "*.html" \)

While for the name pattern with and operator it is not required

find Documents -type f ! -name "*.py" -and ! -name "*.html" 
eQ19
  • 9,880
  • 3
  • 65
  • 77
2
#! /bin/bash
filetypes="*.py *.xml"
for type in $filetypes
do
find Documents -name "$type"
done

simple but works :)

mnrl
  • 1,635
  • 2
  • 17
  • 28
1

I needed to remove all files in child dirs except for some files. The following worked for me (three patterns specified):

find . -depth -type f -not -name *.itp -and -not -name *ane.gro -and -not -name *.top -exec rm '{}' +
Adobe
  • 12,967
  • 10
  • 85
  • 126
0

This works on AIX korn shell.

find *.cbl *.dms -prune -type f -mtime -1

This is looking for *.cbl or *.dms which are 1 day old, in current directory only, skipping the sub-directories.

Chris Mukherjee
  • 841
  • 9
  • 25
0
find MyDir -iname "*.[j][p][g]"
+
find MyDir -iname "*.[b][m][p]"
=
find MyDir -iname "*.[jb][pm][gp]"
0

What about

ls {*.py,*.html}

It lists out all the files ending with .py or .html in their filenames

Code42
  • 2,292
  • 1
  • 17
  • 22