0

My case:

dir1 
  subdir1
      FooFile
  subdir2

dir2 
  subdir1
      FooFile
  subdir2

So, if I cd dir1, I can easily do something like find . | xargs grep "Foo" in order to list the names of all files containing Foo.

The problem is that there are so many subdirectories and files, that this search takes a very long time. I'd like to recursively go into each main dir, and then go specifically only to the ONE subdir (subdir1) and then do the grep.

What is the best way for me to list the names of all files containing Foo from all directories? For every differently named directory, subdir1 is always one level below dir1, and is always named subdir1 exactly.

  • 1
    Wait, are you trying to “list all file names containing Foo” (in which case the base case would be find . | grep Foo, no xargs), or are you trying to list all files containing Foo (which corresponds to your sample code)? – Gilles Mar 16 '16 at 0:42
  • Alas, the OP "joined" Stack Exchange, asked this question, and departed, all in the space of one minute — and did not register.  We may never get the clarification we seek. – G-Man Mar 17 '16 at 5:29
1

If you want to look in and under all (sub)directories named subdir1 that are one level down from the top level, you can do

find */subdir1 …

If you’re looking for files whose names contain Foo, you can do

find */subdir1 -type f -name "*Foo*" -print

Or you could do

find */subdir1 -type f -print | grep "Foo"

but that would include every file under a directory whose name includes the string Foo, for example, dir42/subdir1/Foods_I_Like/pizza and dir42/subdir1/Foods_I_Like/chicken.

If you’re looking for files that contain Foo (i.e., whose contents contain Foo), you can do

find */subdir1 -type f -print | xargs grep "Foo"

Notes:

  • This works because find’s synopsis (usage) looks like
    find [options] [starting-point...] [expression]
    i.e., you can invoke find once on multiple starting points; as in:
    find   dir1/subdir1 dir2/subdir1   [expression]
  • This handles files and directories whose names include space(s) just fine.
  • If you’re looking for files or directories whose names contain Foo, leave out the -type f from the corresponding commands.
  • Of course you must quote "*Foo*" and you must not quote */subdir1.  Quoting "Foo" is optional — it doesn’t hurt, but you don’t need to quote words that don’t contain any special characters.
  • You can leave out the -print, since it’s the default action.
1

what about

grep "foo" */subdir1/*  */subdir1/.*

? (based on your current question, this seems to fit the bill. unless there are so many "subdir1" subdirs that the shell can't supply the whole list to grep.

You can add -q and test if it exit with 0 if you just want to see if a "foo" appears in the file

You can also do a recursive grep, and then discard what you don't need (heavy on i/o, but simple) :

grep -r "foo" . | grep "^\./[^/][^/]*/subdir1/[^/][^/]*:"

(See comments below : here, even if name contains ":", this will work as you did find a filename underneath subdir1, and that file does contain foo. It could miss a file which contains a newline in its name, though, as in the 2nd grep it won't see the first "./firstleveldir/" name, so the 2nd grep will dismiss it...)

  • (1) I don’t think the OP just wants to look in all (sub)directories named subdir1; I think she wants to look in and under (recursively) all (sub)directories named subdir1.  But I agree that the question is a little unclear.  (2) The question says that a search of the entire directory tree is prohibitively slow, so your second answer is probably unacceptable.  … (Cont’d) – G-Man Mar 16 '16 at 7:42
  • (Cont’d) …  (3) Grepping the output of grep to look for the filenames that the first grep found is unreliable.  It can fail if (3a) a filename contains newline, or (3b) a file contains text that looks like a filename.  (You could possibly fix the second one by using [^/:] instead of [^/].)  (4) At the end of your command, you say [^/*] where I believe that you mean [^/]*. – G-Man Mar 16 '16 at 7:42
  • @G-man: thanks, i indeed typoed, and I will edit that. I also agree that adding : inside the regexps would help against some false positive (files with newlines, and other edge cases, really... but you never know). My solution should work when there is nothing too special (no newline in a dirname or filename, etc) – Olivier Dulac Mar 16 '16 at 13:56
  • It occurred to me after I wrote my first comments that testing for : can cause incorrect results if a directory name contains a colon — which, of course, is legal in Unix.  Sometimes you just can't win. – G-Man Mar 16 '16 at 19:46
  • @G-man indeed ^^ ... I ll edit again. shows that it's not a good way on my part, then... – Olivier Dulac Mar 16 '16 at 20:43
0

You can do this with a script, e.g.,

#!/bin/sh
find . -type d | while IFS= read -r pathname
do
    for name in "$pathname"/subdir1/*Foo*
    do
        [ -f "$name" ] && echo "$name"
    done
done

with the usual caveats about newlines in pathnames. Tested with this tree:

./dir2
./dir2/subdir1
./dir2/subdir1/FooFile
./dir2/subdir2
./subdir1
./subdir1/FooFile
./dir spacew
./dir spacew/subdir1
./dir spacew/subdir1/FooFile
./dir spacew/subdir2
./dir1
./dir1/subdir1
./dir1/subdir1/FooFile

the script produced

./subdir1/FooFile
./dir2/subdir1/FooFile
./dir spacew/subdir1/FooFile
./dir1/subdir1/FooFile

The question was ambiguous regarding the use of grep (as written, there was no difference versus echo, except for the possibility of using regular expressions rather than shell wildcards). Alternatively, you could use grep directly:

#!/bin/sh
find . -type f -name '*Foo*' | grep -E 'subdir1/Foo[^/]*$' | while IFS= read -r pathname
do
    echo "$pathname"
done

Further reading:

  • Shouldn't that first line have a -maxdepth 1 operator? – Wildcard Mar 16 '16 at 0:03
  • OP should clarify, but there's no reason subdir1 couldn't have a directory under it also named subdir1 – Thomas Dickey Mar 16 '16 at 0:04
  • (1) I don’t think the OP wants to look in all (sub)directories in the directory tree named subdir1; I think she wants to look in all (sub)directories named subdir1 that are one level down from the top level.  But I agree that the question is a little unclear.  (2) I don’t think the OP just wants to look in all (sub)directories named subdir1; I think she wants to look in and under all (sub)directories named subdir1.  But I agree that the question is a little unclear.  … (Cont’d) – G-Man Mar 16 '16 at 4:27
  • (Cont’d) …  (3) Since the question says “file names containing Foo” and grep "Foo", I believe that the OP wants to find files with names like FavoriteFoods, and so your Foo* glob and your /Foo[^/]*$ regexp are too restrictive.  (4) Why do you say | while IFS= read -r pathname; do echo "$pathname"; done?  Why not just do find . -type f | grep -E 'subdir1/Foo[^/]*$' or even find . -type f -regex '.*/subdir1/Foo[^/]*$'?  P.S. Your grep -E 'subdir1/Foo[^/]*$' matches dir1/xyzzysubdir1/FooFile, which is an unwanted result. – G-Man Mar 16 '16 at 4:29
  • That's several questions. Short: I made the choice to avoid matching filename "subdir1", and did not make the change to use read -r (though in this case it was harmless). And I did not suggest -regex (not POSIX, not derived from the question). – Thomas Dickey Mar 16 '16 at 8:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.