Searching specific lines of files using GREP

Viewed 905

I have a directory with many text files. I want to search a given string in specific lines in the files(like searching for 'abc' in only 2nd and 3rd line of each file). Then When I find A match I want to print line 1 of the matching file.

My Approach - I'm doing a grep search with -n option and storing the output in a different file and then searching that file for the line number. Then I'm trying to get the file name and then print out it's first line.

Using the approach I mentioned above I'm not able to get the file name of the right file and even if I get that this approach is very lengthy.

Is there a better and fast solution to this?

Eg.
1.txt

file 1
one
two

2.txt

file 2
two
three

I want to search for "two" in line 2 of each file using grep and then print the first line of the file with match. In this example that would be 2.txt and the output should be "file 2"

I know it is easier using sed/awk but is there any way to do this using grep?

4 Answers

Use sed instead (GNU sed):

parse.sed

1h                 # Save the first line to hold space
2,3 {              # On lines 2 and 3
  /my pattern/ {   # Match `my pattern`
    x              # If there is a match bring back the first line
    p              # and print it
    :a; n; ba      # Loop to the end of the file
  }
}

Run it like this:

sed -snf parse.sed file1 file2 ...

Or as a one-liner:

sed -sn '1h; 2,3 { /my pattern/ { x; p; :a; n; ba; } }' file1 file2 ...

You might want to emit the filename as well, e.g. with your example data:

parse2.sed

1h                 # Save the first line to hold space
2,3 {              # On lines 2 and 3
  /two/ {   # Match `my pattern`
    F              # Output the filename of the file currently being processed
    x              # If there is a match bring back the first line
    p              # and print it
    :a; n; ba      # Loop to the end of the file
  }
}

Run it like this:

sed -snf parse2.sed file1 file2 | paste -d: - -

Output:

file1:file 1
file2:file 2
$ awk 'FNR==2{if(/one/) print line; nextfile} FNR==1{line=$0}' 1.txt 2.txt
file 1

$ awk 'FNR==2{if(/two/) print line; nextfile} FNR==1{line=$0}' 1.txt 2.txt
file 2
  • FNR will have line number for the current file being read
    • use FNR>=2 && FNR<=3 if you need a range of lines
  • FNR==1{line=$0} will save the contents of first line for future use
  • nextfile should be supported by most implementations, but the solution will still work (slower though) if you need to remove it

With grep and bash:

# Grep for a pattern and print filename and line number
grep -Hn one file[12] |        

# Loop over matches where f=filename, n=match-line-number and s=matched-line
while IFS=: read f n s; do 

  # If match was on line 2 or line 3
  # print the first line of the file
  (( n == 2 || n == 3 )) &&  head -n1 $f
done

Output:

file 1

Only using grep, cut and | (pipe):

grep -rnw pattern dir | grep ":line_num:" | cut -d':' -f 1

Explanation

grep -rnw pattern dir

It return name of the file(s) where the pattern was found along with the line number. It's output will be somthing like this

path/to/file/file1(.txt):8:some pattern 1
path/to/file/file2(.txt):4:some pattern 2
path/to/file/file3(.txt):2:some pattern 3

Now I'm using another grep to get the file with the right line number (for e.g. file that contains the pattern in line 2)

grep -rnw pattern dir | grep ":2:"

It's output will be

path/to/file/file3(.txt):2:line

Now I'm using cut to get the filename

grep -rnw pattern dir | grep ":2:" | cut -d':' -f 1

It will output the file name like this

path/to/file/file3(.txt)

P.S. - If you want to remove the "path/to/file/" from the filename you can use rev then cut and again rev, you can try this yourself or see the code below.

grep -rnw pattern dir | grep ":2:" | cut -d':' -f 1 | rev | cut -d'/' -f 1 | rev

Related