53

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?

Braiam
  • 67,791
  • 32
  • 179
  • 269
Rich
  • 563

6 Answers6

66

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
enzotib
  • 93,831
  • 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 09:46
  • 4
    Append | cut -d ' ' -f2 to get filename only – qwr Jun 11 '18 at 02:08
  • You can also cull the output of head to include a certain number of lines. I only needed the first line, so I used head -n 1 – Timmah Apr 28 '19 at 01:26
  • @qwr wrote "Append | cut -d ' ' -f2 to get filename only".

    Thanks! Although better append | cut -d ' ' -f2 - to avoid problems with filenames that contain spaces.

    – Ganton Jan 28 '20 at 21:34
  • ... and if you want the list sorted from oldest to latest (at the bottom) sort | tail – d0g Aug 08 '21 at 06:45
  • ... and if you only want to see files (not directories) use find . -type f – d0g Aug 08 '21 at 06:46
10

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.

Pat Regan
  • 209
4

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
arrange
  • 14,959
3

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

Prasanth S
  • 1,216
  • 1
    This is the only answer that actually works correctly for me. The other answers produced some files that weren't actually the most recent. Thank you, almost 8 years later. – p1l0t Apr 27 '20 at 18:54
3

Use perl in conjonctin with find :

 find my_directory -type f -printf '%T@\t%p\n' | perl -ane '@m=@F if ($F[0]>$m[0]); END{print $m[1];}'

You get the name of the file with the greatest epoch == last file modified.

1

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

Sergey
  • 43,665