5

I want to execute a command for an each file that matches a pattern. But the command that seems to be right, doesn't work for me, I have no idea why.

$ find . -type f -name '*.c' -or -name '*.h' -or -name '*.cpp' -exec ls {} \;
$ ls
script.sh  test.c  test.h
2
  • Are you just using -exec ls {} \; as an example action or actually wanting to do something with it? (don't see what purpose it serves in the above command)
    – user60101
    Commented Jul 13, 2014 at 1:43
  • Yeah, @Bro, I wanted to execute a custom script to a source code files(btw, you may see the one in ls output above). The ls was chosen just as an example.
    – Hi-Angel
    Commented Jul 13, 2014 at 18:53

3 Answers 3

10

Try adding the expressions into parentheses as stated in the man page:

find . -type f \( -name '*.c' -or -name '*.h' -or -name '*.cpp' \) -exec ls {} \;

should work.

11
  • Yeah, this worked! Thank you very much! Btw, I just searched the man page of the find; I didn't find anything a brackets. What're you talking about?
    – Hi-Angel
    Commented Jul 11, 2014 at 13:39
  • 1
    I was talking about the BSD man page of find, which provides the following example: find / \( -newer ttt -or -user wnj \) -print Sorry I was unclear about it.
    – groxxda
    Commented Jul 11, 2014 at 13:45
  • That's funny though, that the GNU/Linux manual didn't mention so important thing.
    – Hi-Angel
    Commented Jul 11, 2014 at 13:52
  • @YagamyLight The GNU man page uses the more precise word parenthesis to refer to parentheses. So does the FreeBSD man page, for that matter. Commented Jul 11, 2014 at 20:03
  • 1
    @YagamyLight I answered a similar question on SO, which may help you understand this better. In particular, this issue here is your original syntax is treated as find . (-type f AND -name '*.c') OR (-name '*.h') OR (-name '*.cpp' AND -exec ls {} \;)
    – user60101
    Commented Jul 13, 2014 at 1:41
2

With GNU find, you can use -regex option:

find . -type f -regex '.*\.\(c\|h\|cpp\)' -exec ls {} \;
4
  • I'm not sure about the GNU syntax but on BSD this is the correct way to do it with regex: find -E . -type f -regex '.*(c|h|cpp)$' -exec ls {} \;
    – groxxda
    Commented Jul 11, 2014 at 13:43
  • @Groxxda GNU find supports both. It defaults to Emacs regexps, which have a \| operator, unlike FreeBSD, which defaults to BRE. Commented Jul 11, 2014 at 20:05
  • 1
    I was merely talking about the .* vs. *.. After testing the command from this answer on GNU find i have verified it's wrong, but my poor reputation won't let me fix it. I don't understand how a not-working answer is up-voted. JIC here is the working GNU find command: find . -type f -regex '.*\(c\|h\|cpp\)' -exec ls {} \;
    – groxxda
    Commented Jul 12, 2014 at 11:38
  • 1
    @Groxxda: Sorry, my mistypo. I will make it more accurry.
    – cuonglm
    Commented Jul 12, 2014 at 15:06
1

You can try to use find -D tree . [expr..] to understand what find does with your original command.

You must understand that the -type f and also the -exec ls .. expressions are and'ed to the rest of the expressions with higher precedence than the ors.
So your original command will get parsed into something like this: (-type f AND -name *.c) OR -name *.h OR (-name *.cpp AND -exec ls) (note that the or is actually binary and not ternary so a | b | c is in fact (a | b) | c, but you get the point).
Now you will notice that find does not know what to do except for files matching *.cpp as there is no valid statement in the other cases (that's why you may even see a segfault or something similar in the debug output).

I hope this makes it more clear to you why you need the parentheses.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .