title: How can I exclude directories matching certain patterns from the output of the Linux 'find' command?
body: <p>I want to use regex's with Linux's <code>find</code> command to dive recursively into a gargantuan directory tree, showing me all of the .c, .cpp, and .h files, but omitting matches containing certain substrings.  Ultimately I want to send the output to an <code>xargs</code> command to do certain processing on all of the matching files.  I can pipe the <code>find</code> output through grep to remove matches containing those substrings, but that solution doesn't work so well with filenames that contain spaces.  So I tried using <code>find</code>'s -print0 option, which terminates each filename with a nul char instead of a newline (whitespace), and using <code>xargs -0</code> to expect nul-delimited input instead of space-delimited input, but I couldn't figure out how to pass the nul-delimited <code>find</code> through the piped grep filters successfully; grep -Z didn't seem to help in that respect.</p>
  
  <p>So I figured I'd just write a better regex for <code>find</code> and do away with the intermediary <code>grep</code> filters... perhaps <code>sed</code> would be an alternative?</p>
  
  <p>In any case, for the following small sampling of directories...</p>
  
  <pre><code>./barney/generated/bam bam.h
  ./barney/src/bam bam.cpp
  ./barney/deploy/bam bam.h
  ./barney/inc/bam bam.h
  ./fred/generated/dino.h
  ./fred/src/dino.cpp
  ./fred/deploy/dino.h
  ./fred/inc/dino.h
  </code></pre>
  
  <p>...I want the output to include all of the .h, .c, and .cpp files but NOT those ones that appear in the 'generated' and 'deploy' directories.</p>
  
  <p>BTW, you can create an entire test directory (named fredbarney) for testing solutions to this question by cutting &amp; pasting this whole line into your bash shell:</p>
  
  <pre><code>mkdir fredbarney; cd fredbarney; mkdir fred; cd fred; mkdir inc; mkdir docs; mkdir generated; mkdir deploy; mkdir src; echo x &gt; inc/dino.h; echo x &gt; docs/info.docx; echo x &gt; generated/dino.h; echo x &gt; deploy/dino.h; echo x &gt; src/dino.cpp; cd ..; mkdir barney; cd barney; mkdir inc; mkdir docs; mkdir generated; mkdir deploy; mkdir src; echo x &gt; 'inc/bam bam.h'; echo x &gt; 'docs/info info.docx'; echo x &gt; 'generated/bam bam.h'; echo x &gt; 'deploy/bam bam.h'; echo x &gt; 'src/bam bam.cpp'; cd ..;
  </code></pre>
  
  <p>This command finds all of the .h, .c, and .cpp files...</p>
  
  <pre><code>find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$"
  </code></pre>
  
  <p>...but if I pipe its output through xargs, the 'bam bam' files each get treated as two separate (nonexistant) filenames (note that here I'm simply using <code>ls</code> as a stand-in for what I actually want to do with the output):</p>
  
  <pre><code>$ find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$" | xargs -n 1 ls
  ls: ./barney/generated/bam: No such file or directory
  ls: bam.h: No such file or directory
  ls: ./barney/src/bam: No such file or directory
  ls: bam.cpp: No such file or directory
  ls: ./barney/deploy/bam: No such file or directory
  ls: bam.h: No such file or directory
  ls: ./barney/inc/bam: No such file or directory
  ls: bam.h: No such file or directory
  ./fred/generated/dino.h
  ./fred/src/dino.cpp
  ./fred/deploy/dino.h
  ./fred/inc/dino.h
  </code></pre>
  
  <p>So I can enhance that with the -print0 and -0 args to <code>find</code> and <code>xargs</code>:</p>
  
  <pre><code>$ find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$" -print0 | xargs -0 -n 1 ls
  ./barney/generated/bam bam.h
  ./barney/src/bam bam.cpp
  ./barney/deploy/bam bam.h
  ./barney/inc/bam bam.h
  ./fred/generated/dino.h
  ./fred/src/dino.cpp
  ./fred/deploy/dino.h
  ./fred/inc/dino.h
  </code></pre>
  
  <p>...which is great, except that I don't want the 'generated' and 'deploy' directories in the output.  So I try this:</p>
  
  <pre><code>$ find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$" -print0 | grep -v generated | grep -v deploy | xargs -0 -n 1 ls
  barney  fred
  </code></pre>
  
  <p>...which clearly does not work.  So I tried using the -Z option with grep (not knowing exactly what the -Z option really does) and that didn't work either.  So I figured I'd write a better regex for <code>find</code> and this is the best I could come up with:</p>
  
  <pre><code>find . -regextype posix-egrep -regex "(?!.*(generated|deploy).*$)(.+\.(c|cpp|h)$)" -print0 | xargs -0 -n 1 ls
  </code></pre>
  
  <p>...but bash didn't like that (!.*: event not found, whatever that means), and even if that weren't an issue, my regex doesn't seem to work on the regex tester web page I normally use.</p>
  
  <p>Any ideas how I can make this work?  This is the output I want:</p>
  
  <pre><code>$ find . [----options here----] | [----maybe grep or sed----] | xargs -0 -n 1 ls
  ./barney/src/bam bam.cpp
  ./barney/inc/bam bam.h
  ./fred/src/dino.cpp
  ./fred/inc/dino.h
  </code></pre>
  
  <p>...and I'd like to avoid scripts &amp; temporary files, which I suppose might be my only option.</p>
  
  <p>Thanks in advance!
  -Mark</p>