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?

4
  • btw, you do not need none of all those backslashes.
    – enzotib
    Sep 13, 2011 at 9:39
  • @enzotib: you do (\+), otherwise you get find: missing argument to '-exec'
    – arrange
    Sep 13, 2011 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, 2011 at 9:56
  • @enzotib: you're right, my mistake, sorry
    – arrange
    Sep 13, 2011 at 9:59

6 Answers 6

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
6
  • 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, 2011 at 9:46
  • 4
    Append | cut -d ' ' -f2 to get filename only
    – qwr
    Jun 11, 2018 at 2: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, 2019 at 1: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, 2020 at 21:34
  • ... and if you want the list sorted from oldest to latest (at the bottom) sort | tail
    – Dan
    Aug 8, 2021 at 6:45
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.

1
  • 2
    This doesn't handle subdirectories.
    – Clément
    Mar 10, 2019 at 22:33
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
3
  • I confirm, is faster.
    – enzotib
    Sep 13, 2011 at 9:58
  • One more point, ... | sort -r | head -n1 | cut -d " " -f 4- if you want to get the filename only.
    – 林果皞
    Apr 19, 2016 at 10:54
  • I just found sort -r will wrong if filename across multiple lines exist.
    – 林果皞
    Apr 19, 2016 at 11:03
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

1
  • 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, 2020 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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .