I have multiple sub-directories each with different depths. I need to search for the last occurrence of a string in a specific file type (say *.out). How can I accomplish this?

I have tried:

grep -r 'string' **/*.out | tail -1

But that gives me only the last string of the last file.

  • What's the different between the last string of the last file and the last occurrence of a string? – cuonglm May 7 '15 at 17:44
  • If I have 100 directories with each possessing a file of type .out, I need the command to print out the last occurence of string in all 100 files. The above command only prints it out for one file (which I think is the last file accessed) – rambalachandran May 7 '15 at 17:47
  • @cuonglm: The difference is the former omits the relevant occurrences in other files. – lcd047 May 7 '15 at 17:47

I will use something like:

for i in `find . -name "*.out" -type f`;
grep -l 'string' $i
grep 'string' $i|tail -1

With 1st grep you will have filename and below (second grep) the content.

This works as long as the file names don't contain whitespace or \[*?. See cuonglm's answer for a robust solution.

  • Great this works. Is it possible to get the path for the file as well before the grep – rambalachandran May 7 '15 at 17:52
  • From what I know about grep I can print content of only filename. To print both I can use second grep. See my addon to the answer – Romeo Ninov May 7 '15 at 17:56
  • 1
    Thank you. It works in my case. Although I think as discussed in other answer, this command will provide me paths of files that don't contain the string. – rambalachandran May 7 '15 at 18:04
  • No, the script above will print only files, which contain 'string' string inside – Romeo Ninov May 7 '15 at 18:09

Assume you are in parent directory of sub directories:

find . -type f -name '*.out' -exec sh -c '
  for f do
    grep "string" /dev/null "$f" | tail -n 1
' sh {} +
  • This works. Is it possible to obtain the filepath before the grep step? Also what does sh -c & sh {} + do? – rambalachandran May 7 '15 at 17:56
  • Unfortunately this will print the name of every file with this extension (independent if contain or not the string) – Romeo Ninov May 7 '15 at 17:59
  • @WanderingMind: Add /dev/null after "$f" to trick grep to show you the filename. sc -c ... is needed to make the pipe grep | tail work. sh {} + sends the filenames to sh. – lcd047 May 7 '15 at 18:01
  • @WanderingMind: I invite you to read unix.stackexchange.com/a/152723/38906 . – cuonglm May 7 '15 at 18:01
  • 1
    @RomeoNinov: Fixed it! – cuonglm May 7 '15 at 18:02

With zsh on a GNU system:

for f (**/*.out(.)) tac < $f | grep --label=$f -m1 string

With gnu tools, you could use a single invocation of gawk:

awk '/pattern/{l=$0} ENDFILE{if (l) {print FILENAME ":" l; l=""}}' **/*.out

(if line matches store it into l; at the end of each file, if l is not empty print file name and l then reset l)

or sed:

sed -ns '/pattern/h;${x;//{F;p}}' **/*.out

(if line matches, copy to hold space; if la$t line, exchange hold space w. pattern space; if it matches, print File name and line)
though the output is different - file name and corresponding last matching line are on separate lines - but if piped to another sed e.g.

sed -ns '/pattern/h;${x;//{F;p}}' **/*.out |sed '$!N;s/\n/:/'

it will mimic grep output1.
The above assume bash (v. 4 with shopt -s globstar) or zsh. To include hidden files add shopt -s dotglob (bash) or setopt dotglob (zsh).

1: as long none of those file names contains newline characters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.