B.jour
B.jour

Reputation: 543

get last line from grep search on multiple files

I'm curently having some problem with a grep command.

I've found the way to only show the last line of a grep search :

grep PATERN FILE_NAME | tail -1

I also find the way to make a grep search in multiple selected files :

find . -name "FILE_NAME" | xargs -I name grep PATERN name

Now I would like to only get the last line of the grep result for each single file. I tried this :

 find . -name "FILE_NAME" | xargs -I name grep PATERN name | tail -1

This returns me only the last value of the last file where I would like to have the last matching patern for every file.

Upvotes: 49

Views: 143668

Answers (11)

CervEd
CervEd

Reputation: 4243

The sed version

# As soon as we find pattern
# we save that line in hold space
save_pattern_line='/PATTERN/{h;d}'

# switch pattern and hold space
switch_spaces='x'

# At the end of the file
# if the pattern is in the pattern space
# (which we swapped with our hold space)
# switch again, print and exit
eof_print='${/PATTERN/{x;p;d}}'

# Else, switch pattern and hold space
switch_spaces='x'

find . -name 'FILE_NAME' |
  xargs sed -s -n -e $save_pattern_line \
    -e $switch_spaces \
    -e $eof_print \
    -e $switch_spaces

Upvotes: 0

Daniel Frey
Daniel Frey

Reputation: 56863

for f in $(find . -name "FILE_NAME"); do grep PATTERN $f | tail -1; done

Upvotes: 49

Md Saifuddin
Md Saifuddin

Reputation: 1

7years too late to the party. A slow way to modify the Line of command:

find . -name "FILE_NAME" | xargs -I name sh -c "grep PATERN name | tail -1"

If you need to show the file name in each line:

find . -name "FILE_NAME" | xargs -I name sh -c "grep -H PATERN name | tail -1"

Upvotes: 0

Colin Curtin
Colin Curtin

Reputation: 2353

Sort has a uniq option that allows you to select just one line from many. Try this:

grep PATTERN FILENAMES* | tac | sort -u -t: -k1,1

Explanation: Grep will return one line for each match in a file. This looks like:

$ grep match file*
file1.txt:match
file1.txt:match2
file2.txt:match3
file2.txt:match4

And what we want is two lines from that output:

$ ???
file1.txt:match2
file2.txt:match4

You can treat this as a sort of table, in which the first column is the filename and the second is the match, where the column separator is the ':' character.

Our first pipe reverses the output:

$ grep match file* | tac
file2.txt:match4
file2.txt:match3
file1.txt:match2
file1.txt:match

Our second pipe to sort, says: pull out the first unique line (-u), where the key to group by is the first one (-k1,1, key from column 1 to column 1), and we split the data into columns with ':' as a delimiter (-t:). It will also sort our output too! And its output:

$ grep match file* | tac sort -u -t: -k1,1
file1.txt:match2
file2.txt:match4

Upvotes: 13

kvantour
kvantour

Reputation: 26471

An alternative to this could be done with awk instead of grep. A Posix version would read:

awk '(FNR==1)&&s{print s; s=""}/PATTERN/{s=$0}END{if(s) print s}' file1 file2 file3 ...

Using GNU awk, you can use ENDFILE

awk 'BEGINFILE{s=""}/PATTERN/{s=$0}ENDFILE{if(s) print s}' file1 file2 file3 ...

Upvotes: 2

Oo.oO
Oo.oO

Reputation: 13375

Get last line of each file (prefixed with file name). Then, filter output based on pattern.

find . -name "*" -exec tail -v -n1 {} \; | grep "some_string" -B1

on macOS, you have to do it slightly different way

find . -name "*" | xargs tail -1 | grep "some_string" -B1

Upvotes: 0

user2591634
user2591634

Reputation: 13

The quickest way to do this would be get the output last 1 (or more) lines from the files and then grep through that. So -

tail -1 filenames.* | grep "what you want to grep for"

Upvotes: -2

duli
duli

Reputation: 119

You could start with grep's -B (before) parameter. For example to get 5 lines before the match:

duli@i5 /etc/php5/apache2 $ grep -i -B5 timezone php.ini 
[CLI Server]
; Whether the CLI web server uses ANSI color coding in its terminal output.
cli_server.color = On

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
;date.timezone =

Upvotes: 0

glenn jackman
glenn jackman

Reputation: 246774

Another way to find the last line is to reverse the file and output the first match.

find . -name "FILE_NAME" | xargs -I name sh -c 'tac name|sed -n "/PATTERN/{p;q}"'

Upvotes: 0

sotapme
sotapme

Reputation: 4903

There is a solution without the need for loops, this gives what the OP wants.

find . -type f -exec sh -c "fgrep print {} /dev/null |tail -1" \;

./tway.pl:print map(lambda x : x[1], filter(lambda x : x[0].startswith('volume'), globals().items()))
./txml.py:           print("%s does not exist: %s\n" % (host, error))
./utils.py:print combine_dicts(a, b, operator.mul)
./xml_example.py:print ET.tostring(root, method="text")

Compared without the tail -1 gives Too many lines per file but proves the above works.

find . -type f -exec sh -c "fgrep print {} /dev/null" \;

gives:

./tway.pl:print map(lambda x : x[1], filter(lambda x : x[0].startswith('volume'), globals().items()))
./txml.py:           print("%s resolved to --> %s\n" % (host, ip))
./txml.py:           print("%s does not exist: %s\n" % (host, error))
./utils.py:print "a", a
./utils.py:print "b", b
./utils.py:print combine_dicts(a, b, operator.mul)
./xml_example.py:    print ">>"
./xml_example.py:    print ET.tostring(e, method="text")
./xml_example.py:    print "<<"
./xml_example.py:print ET.tostring(root, method="text")

EDIT - remove the /dev/null if you don't want the filename included in the output.

Upvotes: -1

Augusto Hack
Augusto Hack

Reputation: 2170

you can use find to execute commands too:

find . -name "<file-name-to-find>" -exec grep "<pattern-to-match>" "{}" ";" | tail -1

"{}" is the file name, take care with shell globing and expasion when writing the command

Upvotes: 0

Related Questions