Bash – Recursive search for a pattern, then for each match print out the specific SEQUENCE: line number, file name, and no file contents

awkbashfindgrep

What I am after is almost exactly the same as can be found here, but I want the
format "line number, separator, filename, newline" in the results, thus displaying
the line number at the beginning of the line, not after the filename, and without
displaying the line containing the match.

The reason why this format is preferable is that

  • (a) the filename might be long and cryptic and contain the separator which the tool uses to separate the filename from the line number, making it incredibly difficult to use awk to achieve this, since the pattern inside the file might also contain the same separator. Also, line numbers at the beginning of the line will be aligned better than if they appear after the filename. And the other reason for this desired format is that
  • (b) the lines matching the pattern may be too long and mess up the one line per row property on the output displayed on standard out (and viewing the output on standard out is better than having to save to a file and use a tool like
    vi to view one line per row in the output file).

    How can I recursively search directories for a pattern and just print out file names and line numbers

Now that I've set out the requirement, consider this:

  1. Ack is not installed on the Linux host I'm using, so I cannot use it.

  2. If I do the following, the shell executes find . and substitutes 'find .`
    with a list of absolute paths starting at the current working directory and
    proceeding downwards recursively:

    grep -n PATTERN $(find .)
    

    then the -n prints the line number, but not where I want it. Also, for some reason I do not understand, if a directory name includes the PATTERN, then grep matches it in addition to the regular files that contain the pattern. This is not what I want,
    so I use:

    grep -n PATTERN $(find . -type f)
    

    I also wanted to change this command so that the output of find is passed on to
    grep dynamically. Rather than having to build the entire list of absolute paths
    first and then pass the bulk of them to grep, have find pass each line to grep
    as it builds the list, so I tried:

    find . -exec grep -n PATTERN  '{}' \;
    

    which seems like the right syntax according to the man page but when I issue
    this command the Bash shell executes about 100 times slower, so this is not
    the way to go.

In view of what I described, how can I execute something similar to this command
and obtain the desired format. I have already listed the problems associated
with the related post.

Best Answer

Using grep

Why can't you just use the -r switch to grep to recurse the filesystem instead of making use of find? There are 2 additional switches I'd use too, instead of the -n switch.

$ grep -rHn PATTERN <DIR> | cut -d":" -f1-2

Example #1

$ grep -rHn PATH ~/.bashrc | cut -d":" -f1-2
/home/saml/.bashrc:25

Details

  • -r - recursively search through files + directories
  • -H - prints the name of the file if it matches (less restrictive than -l) i.e. it works with grep's other switches
  • -n - display the line number of the match

Example #2

$ grep -rHn PATH ~/.bash* | cut -d":" -f1-2
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25

Using find

$ find . -exec sh -c 'grep -Hn PATTERN "$@" | cut -d":" -f1-2' {}  +

Example

$ find ~/.bash* -exec sh -c 'grep -Hn PATH "$@" | cut -d":" -f1-2' {}  +
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25

If you truly want to use find you can do something like this to exec grep upon finding the files using find.

Related Question