47

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.

Zombo
  • 1
  • 62
  • 391
  • 407
B.jour
  • 523
  • 1
  • 5
  • 10

11 Answers11

48
for f in $(find . -name "FILE_NAME"); do grep PATTERN $f | tail -1; done
congusbongus
  • 13,359
  • 7
  • 71
  • 99
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 1
    Unfortunately, this is not going to be efficient for huge files or complex pattern searches if you are searching whole file and returning just the last result? How about something like the following, in your for loop: `tac file | grep -m1 -oP '(?<=tag>).*(?=)' | head -n 1` or even `grep -m1 -oP '(?<=tag>).*(?=)' <<(tac file)` – kisna Aug 02 '15 at 14:43
  • 2
    see [Why is looping over find's output bad practice?](https://unix.stackexchange.com/questions/321697/why-is-looping-over-finds-output-bad-practice) .. find has `-exec` option to do this and will work even for filenames containing spaces/newlines/etc... – Sundeep Apr 13 '18 at 07:15
11

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
Colin Curtin
  • 2,093
  • 15
  • 17
2

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 ...
kvantour
  • 25,269
  • 4
  • 47
  • 72
0

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

Augusto Hack
  • 2,032
  • 18
  • 35
0

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}"'
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
0

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 =
duli
  • 101
  • 2
0

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
Oo.oO
  • 12,464
  • 3
  • 23
  • 45
0

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"
-1

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.

sotapme
  • 4,695
  • 2
  • 19
  • 20
-1

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
CervEd
  • 3,306
  • 28
  • 25
  • The use of variables for the script is weird, and the use of unquoted variables for the script is a bug. – tripleee Oct 06 '22 at 08:35
  • @tripleee why is it a bug? – CervEd Oct 06 '22 at 08:43
  • [When to wrap quotes around a shell variable](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Oct 06 '22 at 08:44
  • @tripleee There's no white-space but there are special characters the shell would interpret, using double quotes *would* be a bug. Using variables may not be conventional but it allows for clear comments, in the script, for all parts of the sed script. Other feedback on how to improve the answer is still welcome. – CervEd Oct 06 '22 at 08:53
-2

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"
Farvardin
  • 5,336
  • 5
  • 33
  • 54