skip to Main Content

I want to search all files include text "hello" but exclude the result contains "test".

Here is the example files:

mkdir -p /tmp/test
cd /tmp/test

echo "foo hello" > foo.txt

echo "bar world" > bar.txt
echo "test hello" >> bar.txt
echo "world hello" >> bar.txt

Here is the search for "hello":

# find /tmp/test -type f -name '*' -exec grep -H -i "hello" {} ;
/tmp/test/bar.txt:test hello
/tmp/test/bar.txt:world hello
/tmp/test/foo.txt:foo hello

Now I want to exclude "test" from the above search output:

# find /tmp/test -type f -name '*' -exec grep -H -i "hello" {} ; | grep -v "test"
...Nothing here...

Try other pattern:

# find /tmp/test -type f -name '*' -exec grep -H -i "hello" -v "test" {} ;
grep: test: No such file or directory
/tmp/test/bar.txt:bar world
grep: test: No such file or directory

Here is the expected output:

# find /tmp/test -type f -name '*' -exec grep [commands argumensts here] {} ;
/tmp/test/bar.txt:world hello
/tmp/test/foo.txt:foo hello

How to do this search and exclude for find in files?

3

Answers


  1. The issue is that you have test in the file path.

    You can match for things after the : but that will only work if you do not have : in the text of the file.

    find /tmp/test -type f -name '*' -exec grep -H -i 
    

    Example:

    /t/test  ❯❯❯ find /tmp/test -type f -name '*' -exec grep -H -i "hello" {} ; | grep -v -E ".*:.*test"
    /tmp/test/foo.txt:foo hello
    /tmp/test/bar.txt:world hello
    
    Login or Signup to reply.
  2. You can use awk which can parse file contents very quickly. The following sample script

    #!/bin/sh
    
    file1="grepTest1.txt"
    echo "dum dum
    dum hello dum
    crumb test crumb
    dum dum" >${file1}
    
    file2="grepTest2.txt"
    echo "dum dum
    dum hello dum
    dum dum" >${file2}
    
    file3="grepTest3.txt"
    echo "dum dum
    dum test dum
    crumb hello crumb
    dum dum" >${file3}
    
    file4="grepTest4.txt"
    echo "dum dum
    crumb jello crumb
    crumb bellow crumb
    dum test dum
    dum dum" >${file4}
    
    for file in grepTest?.txt
    do
        awk -v acc="hello" -v rej="test" 'BEGIN{
            good=0 ;
        }
        {
            if( index($0, rej) != 0 ){
                good=2 ;
                exit ;
            }else{
                if( index($0, acc) != 0 ){
                    good=1 ;
                    line=$0 ;
                } ;
            } ;
        }
        END{
            if( good == 1 ){
                printf("%s: %sn", FILENAME, line ) ;
            }else{
                if( good == 2 ){
                    printf("REJECT: Found %s at line %s in %s.n", rej, NR, FILENAME ) | "cat >&2" ;
                } ;
            } ;
        }' ${file}
    done
        
    

    gives the output

    REJECT: Found test at line 3 in grepTest1.txt.
    grepTest2.txt: dum hello dum
    REJECT: Found test at line 2 in grepTest3.txt.
    REJECT: Found test at line 4 in grepTest4.txt.
    

    BUT … note that the 3 reject lines go to stderr. if you redirect stdout to a file, you will only have the list of good files in that list.

    Login or Signup to reply.
  3. Use awk instead of grep:

    $ find test -type f -exec awk '/hello/&&!/test/{print FILENAME,$0}' {} ;
    test/foo.txt foo hello
    test/bar.txt world hello
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search