0

I am using grep to search through a large directory, the problem I am having is that I only needed the directories that meet the regular expression. I don't need to have the file names.

The grep command I am using is.

grep -Erin "RegEx" * > outputfile.txt
3
  • 1
    why do you ask for the line number (-n) when you don't care about the filenames? – Jeff Schaller Sep 24 '18 at 17:53
  • ... and why are you using * as operand ? – don_crissti Sep 24 '18 at 18:47
  • grep looks at the contents of files. Please clarify whether you want to search inside the files and then return the directory of the files that contain matches, or whether you want to search for particular filenames and return their directories, or whether you want to search for directory names. – Kusalananda Sep 24 '18 at 20:33
1

Building on what @RalfFriedl said, if you just want the name of the directory whre the file matching the regular expression is located, you can use dirname. If there are multiple files in that directory, you can collapse your output to a sorted unique list using sort. Something like the following:

dirname $(grep -Eril "RegEx" *) | sort -u
1
  • mkdir "test directory" and touch a matching file in there; this breaks if/when any directory names have $IFS characters in them – Jeff Schaller Sep 24 '18 at 18:05
0

Grep doesn't have an option for that.

If you don't want the file names, why do you use option -n for line numbers?

There is an option to list the matching file names:

-l, --files-with-matches
Suppress normal output; instead print the name of each input file from which output would normally have been printed. The scanning will stop on the first match.

You can use sed to delete the last part of the file name and only keep the directory part. If you only want each directory listed once, you can run the output through sort -u.

0

Consider using the find command.

find . -type d -regextype posix-extended -regex 'RegEx'

Looks in current directory, for things of type directory, matching path names using posix-extended (same as grep -E).

7
  • 1
    but this will ask for directories that match the regex, not directories of files that match the regex – Jeff Schaller Sep 24 '18 at 18:03
  • @JeffSchaller Then what did OP mean by, "I only needed the directories that meet the regular expression. I don't need to have the file names." ? – Tyler Marshall Sep 24 '18 at 18:55
  • My understanding is that they want grep to match filenames but to then report back only the containing directories of those files. – Jeff Schaller Sep 24 '18 at 18:56
  • If that is the case, then: find . -type f -regextype posix-extended -regex '.*/RegEx$' | sed -e 's/\/[^/]*$//p' | sort -u. – Tyler Marshall Sep 24 '18 at 19:10
  • I don't think this is about filenames matching a regex, it's about file content... – don_crissti Sep 24 '18 at 19:14
0

Use the -l grep option and post-process it with sed:

grep -rilE "RegEx" * |sed 's![^/]*$!!' > outputfile.txt

The sed expression removes (from the $ end of the line) any characters that are not a forward slash, leaving you with just the directory name(s).

0
# usage: dgrep regex dir
dgrep(){
        find "$2" -type d -print0 | xargs -0 sh -c '
                rex=$1; shift
                for d; do grep -sq "$rex" "$d"/* "$d"/.[!.]* && echo "$d"; done
        ' sh "$1"
}

Unlike the grep -rl + sed solutions, this will only scan the files in directory until a first match is found -- depending on your data, this may speed up things considerably.

Feel free to add your grep options after -sq; a script where they would be passed on the command line is possible, but it would complicate everything with little benefit.

0

On a GNU system:

grep -ErliZ "RegEx" . |
  LC_ALL=C sed -z 's|/[^/]*$||' |
  LC_ALL=C sort -zu |
  tr '\0' '\n'

That's not optimal in that it keeps looking for RegEx in all the files in a directory even after a match has already been found in there.

To avoid that and still avoid running one grep per directory, with GNU awk, you could do:

find . -type f -print0 | LC_ALL=C gawk -v RS='\0' '
  BEGIN{while ((getline < "/dev/stdin") > 0) ARGV[ARGC++] = $0}
  FNR == 1 {dir = FILENAME; sub("/[^/]*$", ""); if (dir in found) nextfile}
  /RegEx/ {found[dir]; print dir; nextfile}'

We're using LC_ALL=C, so sub("/[^/]*$", "") can reliably remove the filename part, but that means that decoding of the text in the files is not done as per the locale's charmap. If you know all the file paths are valid text in the current locale, you can remove it. Or you can add a -name '*' to find to skip file names that contain byte sequences not forming valid characters in the locale.

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