Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

If I want to find the latest file (mtime) in a (big) directory containing subdirectories, how would I do it?

Lots of posts I've found suggest some variation of ls -lt | head (amusingly, many suggest ls -ltr | tail which is the same but less efficient) which is fine unless you have subdirectories (I do).

Then again, you could

find . -type f -exec ls -lt \{\} \+ | head

which will definitely do the trick for as many files as can be specified by one command, i.e. if you have a big directory, -exec...\+ will issue separate commands; therefore each group will be sorted by ls within itself but not over the total set; the head will therefore pick up the lastest entry of the first batch.

Any answers?

share|improve this question
    
btw, you do not need none of all those backslashes. –  enzotib Sep 13 '11 at 9:39
    
@enzotib: you do (\+), otherwise you get find: missing argument to '-exec' –  arrange Sep 13 '11 at 9:48
    
@arrange: I don't have this error, as + has no meaning for bash, so no need to escape it. –  enzotib Sep 13 '11 at 9:56
    
@enzotib: you're right, my mistake, sorry –  arrange Sep 13 '11 at 9:59

5 Answers 5

up vote 14 down vote accepted

You do not need to recur to external commands (as ls) because find can do all you need through the -printf action:

find /path -printf '%T+ %p\n' | sort -r | head
share|improve this answer
1  
Yeah, I came up with find . -type f -exec stat --format=%y \{\} \+ | sort -r | head -n1 but your solution is far cleaner! –  Rich Sep 13 '11 at 9:46

This is on my system faster than printf, though I don't understand why

find /path -type f -exec stat -c "%y %n" {} + | sort -r | head
share|improve this answer
    
I confirm, is faster. –  enzotib Sep 13 '11 at 9:58

I had a similar problem today, but I attacked it without find. I needed something short I could run over ssh to return the most recently edited file in my home directory. This is roughly what I came up with:

ls -tp | grep -v /$ | head -1

The -p option to ls adds a trailing slash to directories, the grep -v removes lines ending in a slash (aka, all directories), and the head -1 limits the output to a single file.

This is much less verbose than using find if all you want to return is the file name.

share|improve this answer

It's not nearly as fashionable, but it's also possible to achieve this with Midnight Commander: search for *, panelize the result, sort by modification time in reverse order.

Obviously, it's a bit slower than find - my home directory, containing 922000 files, was sorted by mc in almost 14 minutes while find spent less than 5 - but there are some benefits:

  • I'd probably spend longer then the 9 minutes difference inventing a proper find invocation :)

  • less chance of an error (forgot to specify -r for sort etc. - start again)

  • it's possible to play with the result set by changing sort order etc. - without re-querying the files.

  • possible to perform file operations only on some files from the result set - i.e. sort by size, delete a few large files which are not needed

share|improve this answer

EDIT: I guess this post is not 'not particularly useful' as I thought it was. This is a really fast solution that just keeps track of the most recently modified file (instead of sorting the entire list of files):

find . -type f -printf '%T@ %p\n' | awk 'BEGIN { mostrecenttime = 0; mostrecentline = "nothing"; } { if ($1 > mostrecenttime) { mostrecenttime = $1; mostrecentline = $0; } } END { print mostrecentline; }' | cut -f2- -d ' '

Spread over multiple lines for clarity it looks as follows:

find . -type f -printf '%T@ %p\n' | awk '
    BEGIN { mostrecenttime = 0; mostrecentline = "nothing"; }
    {
        if ($1 > mostrecenttime)
            { mostrecenttime = $1; mostrecentline = $0; }
    }
    END { print mostrecentline; }' | cut -f2- -d ' '

End of EDIT


Not a particularly useful post but since 'arrange' was discussing speed, I thought I'd share this.

arrange's and enzotib's solutions involve listing all files inside the directory with their mtimes and then sorting. As you know sorting is not necessary to find the maximum. Finding maximum can be done in linear time but sorting takes n log(n) time [I know the difference isn't much, but still ;)]. I can't think of a neat way of implementing this. [EDIT: A neat (albeit dirty looking) and fast implementation provided above.]

Next best thing - To find the most recently edited file in a directory, recursively find the most recently edited file in each level 1 subdirectory. Let this file represent the subdirectory. Now sort the level 1 files along with the representatives of the level 1 subdirectories. If the number of number of level 1 files and sub-dirs of each directory is nearly a constant, then this process should scale linearly with total number of files.

This is what I came up with to implement this:

findrecent() { { find "$1" -maxdepth 1 -type f -exec stat -c "%y %n" {} + | sort -r | head -1 && find "$1" -mindepth 1 -maxdepth 1 -type d -exec findrecent {} \;; } | sort -r | head -1; }
findrecent .

I ran this and got a bunch of find: findrecent: No such file or directory errors. Reason: -exec of find runs in a different shell. I tried defining findrecent in .bashrc, .xsessionrc but these didn't help [I'd appreciate help here]. In the end I resorted to putting

#!/bin/bash
{ find "$1" -maxdepth 1 -type f -exec stat -c "%y %n" {} + | sort -r | head -1 && find "$1" -mindepth 1 -maxdepth 1 -type d -exec findrecent {} \;; } | sort -r | head -1;

in a script called findrecent in my PATH and then running it.

I ran this, kept waiting and waiting with no output. Just to be sure I wasn't dealing with any infinite loops I modified the file to

#!/bin/bash
echo "$1" >&2
{ find "$1" -maxdepth 1 -type f -exec stat -c "%y %n" {} + | sort -r | head -1 && find "$1" -mindepth 1 -maxdepth 1 -type d -exec findrecent {} \;; } | sort -r | head -1;

and tried again. It did work - but took 1 minute 35 seconds on my homefolder - arrange's and enzotib's solutions took 1.69, 1.95 seconds respectively!

So much for O(n)'s superiority over O(n log (n))! Damn you function call overhead! [Or rather script call overhead]

But this script does scale better than the earlier solutions and I bet it'll run faster than them on google's memory bank ;D

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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