17

I used this a lot, the improvement I try to achieve is to avoid echo file names that did not match in grep. Better way to do this?

    for file in `find . -name "*.py"`; do echo $file; grep something $file; done
25
find . -name '*.py' -exec grep something {} \; -print

would print the file name after the matching lines.

find . -name '*.py' -exec grep something /dev/null {} +

would print the file name in front of every matching line (we add /dev/null for the case where there's only one matching file as grep doesn't print the file name if it's passed only one file to look in. The GNU implementation of grep has a -H option for that as an alternative).

find . -name '*.py' -exec grep -l something {} +

would print only the file names of the files that have at least one matching line.

To print the file name before the matching lines, you could use awk instead:

find . -name '*.py' -exec awk '
  FNR == 1 {filename_printed = 0}
  /something/ {
    if (!filename_printed) {
      print FILENAME
      filename_printed = 1
    }
    print
  }' {} +

Or call grep twice for each file - though that'd be less efficient as it would run at least one grep command and up to two for each file (and read the content of the file twice):

find . -name '*.py' -exec grep -l something {} \; \
                    -exec grep something {} \;

In any case, you don't want to loop over the output of find like that and remember to quote your variables.

If you wanted to use a shell loop, with GNU tools:

find . -name '*.py' -exec grep -l --null something {} + |
   xargs -r0 sh -c '
     for file do
       printf "%s\n" "$file"
       grep something < "$file"
     done' sh

(also works on FreeBSD and derivatives).

| improve this answer | |
6

If you're using GNU grep, you can use its -r or --recursive option to do this simple find for you:

grep -r --include '*.py' -le "$regexp" ./ # for filenames only
grep -r --include '*.py' -He "$regexp" ./ # for filenames on each match

You only need find if you need more advanced predicates.

| improve this answer | |
  • 1
    Depending on the version of GNU grep, grep may or may not look inside symlinks or traverse symlinks to directories. You may also find some variations in the handling of other types of non-regular files. – Stéphane Chazelas Feb 2 '17 at 13:23
5

You can tell grep to include the filename in the output. So if there is a match it will be shown on the console; if there is no match within a file, no line will be printed for that file.

find . -name "*.py" | xargs grep -n -H something

From the man grep:

-H       Always print filename headers with output lines
-n, --line-number
         Each output line is preceded by its relative line number in the file, starting at line 1.  The line number counter is reset for each file processed.
         This option is ignored if -c, -L, -l, or -q is specified.

If your files might have names with spaces in them, you have to switch the pipe to use NUL Characters as a seperator. The full command will now look like this:

find . -name "*.py" -print0 | xargs -0 grep -n -H something
| improve this answer | |
1

You can try something like:

find . -name "*.py:" -exec grep -l {} \;

This exec grep command for every file, discovered by find command and its standard find command feature

| improve this answer | |
1

Use the -l argument.

for file in `find . -name "*.py"`; do grep -l something $file && grep something $file; done

A more findish usage would be:

for file in $(find . -name '*.py' -exec grep -l something '{}' +); do echo "$file"; grep something $file; done
| improve this answer | |
1

There are grep alternatives that by default output their results in the format you want. The 2 most popular ones that I know are ag (a.k.a. "the silver searcher") and ack. ag is advertised as a faster alternative to ack.

$ ag '^\w+\s*\w+\(' ~/build/i3/src
build/i3/src/display_version.c
58:void display_running_version(void) {

build/i3/src/load_layout.c
42:static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings =
518:json_content_t json_determine_content(const char *filename) {
575:void tree_append_json(Con *con, const char *filename, char **errormsg) {

build/i3/src/x.c
64:CIRCLEQ_HEAD(state_head, con_state) state_head =
67:CIRCLEQ_HEAD(old_state_head, con_state) old_state_head =
70:TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head =
97:void x_con_init(Con *con, uint16_t depth) {
...

I can't show you here, but the output is neatly colored. I get the filenames in an olive green, the line numbers in gold yellow, and the matched piece in each line in blood red. The colors are customizable though.

| improve this answer | |

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.