0

I'm using this find command to search all files in a directory ending with .err and .out. I'm using -exec with grep to search some content inside the files. But I can't figure out how to redirect each grep command to a file.

This my command:

find ./Documents/ -type f \( -name "*.out" -o -name "*.err" \) -exec sh -c "grep out {}; grep err {}" \;

I have tried this but it does not work (the files created are empty):

find ./Documents/ -type f \( -name "*.out" -o -name "*.err" \) -exec sh -c "grep out > file1 {}; grep err > file2 {}" \;

How can I solve this problem?

cigien
  • 57,834
  • 11
  • 73
  • 112
  • For each file which `find` passes to your `-exec`, you destroy the old content of `file1` and `file2`. Overall, `file1` and `file2` contain the grep-result of the last file processed by `find`. If this file does not contain the pattern, the result files are empty. – user1934428 Oct 19 '22 at 06:50
  • BTW, if you write the grepping as `grep -E 'err|out'`, you need only a single grep, though you may want consider using the `-w` option as well. Check the _grep_ man page for this. – user1934428 Oct 19 '22 at 06:53
  • Are you really interested in knowing only the matching lines, or do you also want to know the filenames where the pattern occurs? This information is lost in your approach. – user1934428 Oct 19 '22 at 06:54

2 Answers2

2

The conventional syntax is grep pattern inputfile >outputfile so that's what I would suggest.

However, you also should avoid {} inside -exec sh -c '...' because it will break if the file name contains shell metacharacters. You are creating a new shell inside the single quotes and inside that script, file names with, for example, single quotes need to have them escaped or otherwise handled.

Fortunately, the workaround is simple; variables which contain these charachers are fine (but you need to double-quote the variable interpolation, too!)

I'm also guessing you don't want to overwrite the results from the previous -exec each time find finds a new file, so I changed > to >>.

find ./Documents/ -type f \( \
    -name "*.out" -o -name "*.err" \) \
    -exec sh -c 'grep out "$1">>file1
        grep err "$1" >>file2
        ' _ {} \;

Another improvement would be to use -exec ... {} + to run a single -exec for as many files as possible; you will then want to add a loop to the inner script.

find ./Documents/ -type f \( \
    -name "*.out" -o -name "*.err" \) \
    -exec sh -c 'for f; do
        grep out "$f">>file1
        grep err "$f">>file2
    done' _ {} +

Depending on your use case, you might be able to get rid of the loop and just run grep directly on all the files.

find ./Documents/ -type f \( \
    -name "*.out" -o -name "*.err" \) \
    -exec sh -c '
        grep -H out "$@">>file1
        grep -H err "$@">>file2
    ' _ {} +

The -H option turns off reporting of the file name for each match, which is otherwise enhbled by default if you run grep on more than one file.

See also When to wrap quotes around a shell variable? and https://mywiki.wooledge.org/BashFAQ/020

tripleee
  • 175,061
  • 34
  • 275
  • 318
0

You have wrong sequence in grep command. You should also redirect to append using '>>'. Otherwise your previous grep output will be overwritten by the next file found by the find command.

Try this command below:

find ./Documents/ -type f \( -name "*.out" -o -name "*.err" \) -exec sh -c "grep 'out' {} >> file1; grep 'err' {} >> file2" \;
fauzimh
  • 594
  • 4
  • 16
  • 1
    You also should avoid `{}` inside `-exec sh -c '...'` because it will break if the file name contains shell metacharacters. – tripleee Oct 19 '22 at 05:47