Take the 2-minute tour ×

I have a requirement to search for a pattern in multiple files and get the latest file for further processing. I am trying to do this in a shell script. I was trying to do it as follows

file=`grep -lh <pattern> <file_name> | tail -1`

But grep is listing the files as in a ls and not as in ls -lrt. I have tried the following command

ls -lrt `grep -l <pattern> <file name>`

But I'm not able to incorporate this command in the shell script. Any help is appreciated.


share|improve this question

5 Answers 5

This is much faster. Whereas the other answers grep all the files, this searches the newest ones first and stops as soon as it finds the pattern.

This one-liner captures the result in a variable:

file=$(while read file; do grep pattern "$file" >/dev/null;[[ $? ]]; then echo "$file"; break; fi; done < <(find $startdir -maxdepth 1 -type f -printf "%T@:%p\n"|sort -nr|cut -d: -f2-))

Here is a Bash script version that is easier to read:

while read file
    grep pattern "$file" > /dev/null
    if [[ $? ]]
        echo "$file"
            sort -nr |
            cut -d: -f2-)
share|improve this answer
+1 Looks like it should also handle spaces in filenames. –  retracile Oct 24 '09 at 2:05
True. But not newlines. ~shudder~ –  Dennis Williamson Oct 24 '09 at 2:36

you can pipe the output of grep to xargs to use ls -lrt before piping through tail

share|improve this answer

This should do it:

ls -lrt|awk '{print $9}'|xargs -n1 grep <string>|tail -n1|awk '{print $1}'

You may need to change which variables in awk get printed, depending on your implementation of ls and grep.

Basically, sort the files first, then look for the string, then take the last result.

share|improve this answer
bluerain ~ # ls -lrt `grep -l a *.sh `
    -rwxr-xr-x 1 root root 315 2007-01-20 17:43 twopass.sh
    -rwxr-xr-x 1 root root  86 2007-04-21 16:23 hd_down.sh
    -rwxr-xr-x 1 root root 245 2009-09-10 19:47 pspenc.sh
    -rwxr--r-- 1 root root  95 2009-09-10 19:50 psp2.sh 
bluerain ~ # kk=`grep -l a *.sh` 
bluerain ~ # bb=` ls -rt $kk | tail -1` 
bluerain ~ # echo $bb psp2.sh 
bluerain ~ # echo $kk hd_down.sh psp2.sh pspenc.sh twopass.sh

so use the following:

tempvar=`grep -l <pattern> *(for all files in the directory)`
file=`ls -rt $tempvar | tail 1`
share|improve this answer
This works. Thanks a lot –  Ashu Oct 23 '09 at 22:26

In your first example, grep should not be re-sorting the filenames you pass to it. If you are passing them explicitly, it will print the output in the same order as the list of files you pass it. However, if you are passing a wildcard, you are correct that your shell will expand that wildcard in lexical order (which is basically what ls defaults to, as you noted).

After looking at the other suggestions, I offer the following:

file=`ls -t --quoting-style=shell "dir" | xargs grep -l "regex" | head -n 1`

I think this meets your requirements, and addresses concerns raised by others as follows:

  1. You can use ls -t without -l, so there's no need to parse the output of ls -l with awk or cut just to get the filename.
  2. Don't reverse the sort order of ls. Using ls -t puts the newest files first, and grabbing the first one with head will short-circuit the pipeline as soon as the first matching file is found (so you search the minimal list of files necessary to find a match).
  3. Piping to xargs effectively lets you search an unlimited number of files without running into the maximum command line length, and does so without looping.
  4. Using ls --quoting-style=shell quotes filenames with single quotes as needed, so filenames with embedded spaces or control characters are handled properly.

Please note that --quoting-style is a GNU extension and is not likely to work with a non-GNU ls. You should be fine on Linux. The following is more portable, but adds a call to sed to quote the filenames, so there's a little more overhead:

file=`ls -t "dir" | sed 's/\(.*\)/"\1"/' | xargs grep -l "regex" | head -n 1`
share|improve this answer

Your Answer

