1

I have a directory ./src with files. I would like to extract all .c files in it, except those .c files that are in ./src/test. I have tried variants of

find ./src -name "*.c" -and -not -name ./src/test

(inspired from here) but none have succeeded.

What am I doing wrong?

Community
  • 1
  • 1
Randomblue
  • 112,777
  • 145
  • 353
  • 547

3 Answers3

3

Can you use grep ?

find ./src -name \*.c | grep -v ./src/test

lukecampbell
  • 14,728
  • 4
  • 34
  • 32
  • @Jonathan: sure you can. `-print` is not the only action of `find`. – n. m. could be an AI Apr 30 '12 at 17:13
  • @n.m. How do you get `-prune` to work? Show me, even though I'm not from Missouri. (And what's the relevance of `-print`? It is the default action except on positively archaic versions of `find`.) – Jonathan Leffler Apr 30 '12 at 17:15
  • `find . -path ./src/test -prune -o -name \*.c -print`. With -path you can also do things like `-not -path ./src/test/*` i.e. with no prune, but it's inefficient. – n. m. could be an AI Apr 30 '12 at 17:31
  • If you want something which is not -print, there's nothing to grep. – n. m. could be an AI Apr 30 '12 at 17:33
  • I'm trying to grasp... what's wrong with the above? It works fine for me... `find ./src -name \*.c | grep -v ./src/test` – lukecampbell Apr 30 '12 at 17:56
  • There's nothing much wrong with what you suggested, unless you take the Hudson's viewpoint that if there's a file in `./src/test/subdir` it should be printed (which is left ambiguous in the question). We might also argue that files in `./src/testdir` should be printed, but the `grep -v` you quoted would remove those too. Not a biggie, but that's why there's a slash after `test` in my `grep -v`. The rest of the discussion here is about `-prune`, partly as a reaction to the comment by n.m. that we can both be wrong. I was expecting n.m. to post an answer; he's not done so yet. – Jonathan Leffler Apr 30 '12 at 18:49
  • My thought was not that `./src/test/subdir` should be printed, but that the contents of `./src/bar./src/test` would be excluded since the regex to grep is not anchored. – Hudson May 01 '12 at 01:40
3

I'd use a grep -v post-filter, assuming you don't have newlines in your paths (spaces will be OK):

find ./src -name "*.c" | grep -v '/src/test/'

I think your trouble is that the -name looks at the last element of the path only, so a -name with slashes in it simply doesn't work.

If your version of find supports it, using -path in place of -name might work:

find ./src -name "*.c" -and -not -path './src/test/*'

Note the modified operand to -path.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • find without -print0 is a huge security and correctness issue. This will also discard any subdirectories names ./foo/src/test that occur deeper in the directory tree, not just the ones in ./src/test – Hudson Apr 30 '12 at 17:18
  • @Hudson: it depends on what you're up to. For a list of files to the screen, `-print0` is no help whatsoever. For a list of files for subsequent processing, then `-print0` is a good option, but so too is `-exec whatever {} +` (which avoids the overhead of `xargs`). At this stage, the issue is the predicates, not the processing of the output. (I've upvoted your answer already.) – Jonathan Leffler Apr 30 '12 at 17:22
  • @Hudson: The filtration issue is open to interpretation. You're correct that the `grep -v` will remove any files in any sub-directories of `src/test`, but that was my interpretation of the required result. If that isn't what's wanted, then you need a slightly more complex pattern: `grep -v '\./src/test/[^/]*$'`. That only deletes files in `./src/test/` directory. – Jonathan Leffler Apr 30 '12 at 17:29
  • Sure, to the screen -print is fine. But when combined with xargs it is almost always dangerous to not use the nul-separated -print0. xargs will likely have lower overhead than "-exec ... {}" since xargs will only create one (or a small number) of sub-processes, while -exec will create one per file. – Hudson Apr 30 '12 at 18:24
  • @Hudson: You missed the `+` instead of `;` in my command; that makes `find` pretend to be `xargs`, saving up arguments until some limit is reached and only then performing the command, rather like `xargs` does, but without the `-print0` option. As to danger in general, that depends on the rules under which your system operates. As a general purpose system, yes, you have to be paranoid about spaces and newlines etc in file names. However, if your development rules say "Thou shalt stick to portable file names" then you do not have to be so paranoid. It depends on context. – Jonathan Leffler Apr 30 '12 at 18:32
  • *1,000,000 for "a -name with slashes in it simply doesn't work". – Mawg says reinstate Monica Nov 25 '14 at 08:06
2

You want -regex to match the entire path (-name only matches the filename in the current directory) and -prune to eliminate that part of the tree:

find ./src -regex '^./src/test$' -prune -o -name '*.c' -print0 | xargs -0 ....

find without -print0 is almost always a problem waiting to happen if someone creates a filename with a space in it (or worse a newline).

Hudson
  • 2,001
  • 1
  • 15
  • 17
  • Can you sensibly use `-prune` without a `-o` alternative? – Jonathan Leffler Apr 30 '12 at 17:29
  • Yes, but not often. find . -name CVS -prune will execute the default -print on the CVS directories. The man page says "If the expression contains no actions other than -prune, -print is performed on all files for which the expression is true." – Hudson Apr 30 '12 at 18:22
  • In a comment, use backticks to surround code fragments (and avoid trying to write backticks as backticks in a comment!). As you can see, the `` markup doesn't work. – Jonathan Leffler Apr 30 '12 at 18:37