254

How can I do a simple find which would order the results by most recently modified?

Here is the current find I am using (I am doing a shell escape in PHP, so that is the reasoning for the variables):

find '$dir' -name '$str'\* -print | head -10

How could I have this order the search by most recently modified? (Note I do not want it to sort 'after' the search, but rather find the results based on what was most recently modified.)

Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90

18 Answers18

324

Use this:

find . -printf "%T@ %Tc %p\n" | sort -n

printf arguments from man find:

  • %Tk: File's last modification time in the format specified by k.

  • @: seconds since Jan. 1, 1970, 00:00 GMT, with fractional part.

  • c: locale's date and time (Sat Nov 04 12:02:33 EST 1989).

  • %p: File's name.

Dennis
  • 48,917
  • 12
  • 130
  • 149
user195696
  • 3,241
  • 1
  • 12
  • 2
  • 11
    +1 Very useful, the first answer to this I have found with a readable/useful date output – Jake N Jun 26 '13 at 13:48
  • most reliable (and very simple) as the time is granted to be numerically sequential (therefore always properly sortable), thx! – Aquarius Power Oct 26 '14 at 01:31
  • 3
    I have this alias for finding recent files in my `~/.zshrc`: `fr () { find ./ -iname "*"$@"*" -printf "%T@ %Td-%Tb-%TY %Tk:%TM %p\n" | sort -n | cut -d " " -f 2- | grep -i "$@" ; }` It recursively finds all files containing the pattern of the first argument passed to the command (`fr `) and sorts them with the most recent one last. – joelostblom Oct 27 '16 at 23:01
  • This is great !!! To use with symlinks, use `find -L ... ` – Varun Chandak Jan 21 '19 at 09:49
  • 2
    You may want to use `ssed` to get rid of the seconds fractional part and stil use ISO8601 as @PeterMortensen showed : `find . -type f -printf "%TY-%Tm-%TdT%TT %p\n" | sort -r | ssed -R 's/^([^.]+)\.\d+ (.*)$/\1 \2/'` – Ludovic Kuty Jan 31 '19 at 08:51
  • 1
    @joelostblom JFYI, instead of `cut -d " " -f 2-` you may want to use `cut -d " " -f 1 --complement` but it is longer. – Ludovic Kuty Jan 31 '19 at 08:52
  • For [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) date output with ***time zone information***, use `find . -printf "%TY-%Tm-%TdT%TT %TZ %p\n" `. As it sorts naturally, it can also, like the Unix time in this answer, be used directly in the sort: `find . -printf "%TY-%Tm-%TdT%TT %TZ %p\n" | sort -n`. Note that the date information is not in ISO 8601 format (e.g. "CEDT" - Central European Daylight Time). (*My original comment (now deleted) had a number of flaws, in particular the use of the access time instead of the modification time.*) – Peter Mortensen Apr 17 '19 at 14:19
  • 6
    Note that for OSX one has to use the GNU version `gfind` instead of `find`, since `printf` is not part of `POSIX` `find`. It can be installed with `brew install findutils`. – ttq Jun 08 '21 at 16:14
  • getting the error `paths must precede expression`? Make sure you got `-printf` and not `-print` – phdoerfler Feb 25 '22 at 15:21
  • Why not just use `"%T+ %p\n"`, `%T+` include date and time with fractional seconds – Chan Oct 15 '22 at 23:51
  • i like this solution and added ` | cut -d' ' -f2-` to get rid of the sort column in the final output. – Kevin Olree Apr 07 '23 at 20:13
  • Thank you, contrary to many other answers this works when find has multiple search directories – Charles G Aug 25 '23 at 15:30
95

The easiest method is to use zsh, thanks to its glob qualifiers.

print -lr -- $dir/**/$str*(om[1,10])

If you have GNU find, make it print the file modification times and sort by that.

find -type f -printf '%T@ %p\0' |
sort -zk 1nr |
sed -z 's/^[^ ]* //' | tr '\0' '\n' | head -n 10

If you have GNU find but not other GNU utilities, use newlines as separators instead of nulls; you'll lose support for filenames containing newlines.

find -type f -printf '%T@ %p\n' |
sort -k 1nr |
sed 's/^[^ ]* //' | head -n 10

If you have Perl (here I'll assume there are no newlines in file names):

find . -type f -print |
perl -l -ne '
    $_{$_} = -M;  # store file age (mtime - now)
    END {
        $,="\n";
        @sorted = sort {$_{$a} <=> $_{$b}} keys %_;  # sort by increasing age
        print @sorted[0..9];
    }'

If you have Python (also assuming no newlines in file names):

find . -type f -print |
python -c 'import os, sys; times = {}
for f in sys.stdin.readlines(): f = f[0:-1]; times[f] = os.stat(f).st_mtime
for f in (sorted(times.iterkeys(), key=lambda f:times[f], reverse=True))[:10]: print(f)'

There's probably a way to do the same in PHP, but I don't know it.

If you want to work with only POSIX tools, it's rather more complicated; see How to list files sorted by modification date recursively (no stat command available!) (retatining the first 10 is the easy part).

phuclv
  • 26,555
  • 15
  • 113
  • 235
Gilles 'SO- stop being evil'
  • 69,786
  • 21
  • 137
  • 178
  • I think the `find` version shows the oldest files, and that you need to add the `-r` option to `sort`. – Quentin Pradet Sep 07 '12 at 06:56
  • My sed says it doesn't have a -z option. – Kef Schecter Jul 15 '15 at 04:38
  • 1
    @KefSchecter Then use newlines as separators, but you'll lose support for newlines in file names. – Gilles 'SO- stop being evil' Jul 15 '15 at 08:15
  • 1
    The above is for python2. If you only have python3, some small changes: python3 -c 'import os, sys; times = {} for f in sys.stdin.readlines(): f = f[0:-1]; times[f] = os.stat(f).st_mtime for f in (sorted(times.keys(), key=lambda f:times[f], reverse=True))[:10]: print(f);' – Goblinhack Dec 12 '17 at 15:33
  • Your #2 solution works (it's the only one I've tried). The answer checked as "valid" doen't work properly (not the last touched files). – Olivier Pons Apr 08 '22 at 07:31
  • As "python" is not a stable language, you should always write even oneliners with `python2` or `python3` instead. – Mikko Rantalainen Nov 16 '22 at 09:27
  • @MikkoRantalainen Back when I wrote this answer, Python 3 was still pretty new and uncommon. Later, some systems decided only ship `python`, so explicitly calling `python2` or `python3` is unfortunately not portable either. – Gilles 'SO- stop being evil' Nov 16 '22 at 09:47
  • Yeah, people once believed that "python" was a stable language but Python designers decided to break backwards compatibility and we're here to collect pieces. Nowadays oneliners must explicitly say `python2` or `python3` unless the code is specifically compatible with all Python versions (including future ones because there's already precedent for breaking backwards compatibility). – Mikko Rantalainen Nov 16 '22 at 14:56
59

You don't need to PHP or Python, just ls:

man ls:
-t     sort by modification time
-r,    reverse order while sorting (--reverse )
-1     list one file per line

find /wherever/your/files/hide -type f -exec ls -1rt "{}" +;

If command * exits with a failure status (ie Argument list too long), then you can iterate with find. Paraphrased from: The maximum length of arguments for a new process

  • find . -print0|xargs -0 command (optimizes speed, if find doesn't implement "-exec +" but knows "-print0")
  • find . -print|xargs command (if there's no white space in the arguments)

If the major part of the arguments consists of long, absolute or relative paths, then try to move your actions into the directory: cd /directory/with/long/path; command * And another quick fix may be to match fewer arguments: command [a-e]*; command [f-m]*; ...


2023 update

The xargs solution does not really work, because it will output in several batches, so you would find the latest file in each batch, as opposed to the latest file of all.

A practical solution with gnu-find can be found in my local-bin repo:

reproduced here:

#!/bin/bash
function strip_1() { awk ' BEGIN{ RS="\0"} { $1="" ; gsub(/^\s/,"", $0); print }'; }
function strip_1n() { awk '{ $1="" ; gsub(/^\s/,"", $0); print }'; }
function n2z() { tr \\n \\0; }
find=/usr/bin/find
if [[ $0 == *latest0 ]]; then
    $find ${1:-./} -not -path '*/.git/*' -type f -printf "%T@ %p\0" | sort -nz | n2z | strip_1 | n2z
else
    $find ${1:-./} -not -path '*/.git/*' -type f -printf "%T@ %p\n" | sort -n | strip_1n
fi

19

Extending user195696's answer:

find . -type f -printf "%T@\t%Tc %6k KiB %p\n" | sort -n | cut -f 2-

For each file, this first outputs the numeric timestamp (for sorting by, followed by tabulation \t), then a human-readable timestamp, then the filesize (unfortunately find's -printf can't do in mebibytes, only kibibytes), then the filename with relative path.

Then sort -n sorts it by the first numeric field.

Then cut gets rid of that first numeric field which is of no interest to the user. (Prints second field onward.) The default field separator is \t or tabulation.

Example of output:

Thu 06 Feb 2014 04:49:14 PM EST     64 KiB ./057_h2_f7_10/h2_f7_10.class
Fri 07 Feb 2014 02:08:30 AM EST 7962976 KiB ./056_h2_f7_400/h2__rh_4e-4.mph
Fri 07 Feb 2014 02:23:24 AM EST 7962976 KiB ./056_h2_f7_400/h2_f7_400_out_Model.mph
Fri 07 Feb 2014 02:23:24 AM EST      0 KiB ./056_h2_f7_400/h2_f7_400_out.mph.status
Fri 07 Feb 2014 02:23:24 AM EST     64 KiB ./056_h2_f7_400/1579678.out
Fri 07 Feb 2014 03:47:31 AM EST 8132224 KiB ./057_h2_f7_10/h2__rh_1e-5.mph
Fri 07 Feb 2014 04:00:49 AM EST 8132224 KiB ./057_h2_f7_10/h2_f7_10_out_Model.mph
Fri 07 Feb 2014 04:00:49 AM EST      0 KiB ./057_h2_f7_10/h2_f7_10_out.mph.status
Fri 07 Feb 2014 04:00:49 AM EST     64 KiB ./057_h2_f7_10/1579679.out
Fri 07 Feb 2014 09:47:18 AM EST   9280 KiB ./056_h2_f7_400/h2__rh_4e-4.mat
Fri 07 Feb 2014 10:51:23 AM EST   9728 KiB ./018_bidomain/h2_plain__rh_1e-5.mat
Fri 07 Feb 2014 10:58:33 AM EST   9568 KiB ./057_h2_f7_10/h2__rh_1e-5.mat
Fri 07 Feb 2014 05:05:38 PM EST     64 KiB ./058_h2_f7_stationary/h2_f7_stationary.java
Fri 07 Feb 2014 06:06:29 PM EST     32 KiB ./058_h2_f7_stationary/slurm.slurm
Sat 08 Feb 2014 03:42:07 AM EST      0 KiB ./058_h2_f7_stationary/1581061.err
Sat 08 Feb 2014 03:42:14 AM EST     64 KiB ./058_h2_f7_stationary/h2_f7_stationary.class
Sat 08 Feb 2014 03:58:28 AM EST  70016 KiB ./058_h2_f7_stationary/h2s__rh_1e-5.mph
Sat 08 Feb 2014 04:12:40 AM EST  70304 KiB ./058_h2_f7_stationary/h2s__rh_4e-4.mph
Sat 08 Feb 2014 04:12:53 AM EST  70304 KiB ./058_h2_f7_stationary/h2_f7_stationary_out_Model.mph
Sat 08 Feb 2014 04:12:53 AM EST      0 KiB ./058_h2_f7_stationary/h2_f7_stationary_out.mph.status
Sat 08 Feb 2014 04:12:53 AM EST     32 KiB ./058_h2_f7_stationary/1581061.out
Mon 10 Feb 2014 11:40:54 AM EST    224 KiB ./058_h2_f7_stationary/h2s__rh_4e-4.mat
Mon 10 Feb 2014 11:42:32 AM EST    224 KiB ./058_h2_f7_stationary/h2s__rh_1e-5.mat
Mon 10 Feb 2014 11:50:08 AM EST     32 KiB ./plot_grid.m

I deliberately made the filesize field 6 characters, because if making it longer, it becomes hard to visually distinguish how large the files are. This way, files larger than 1e6 KiB jut out: by 1 char means 1-9 GB, by 2 chars means 10-99 GB, etc.


Edit: here's another version (since find . -printf "%Tc" crashes on MinGW/MSYS):

find . -type f -printf "%T@\t%p\n" | sort -n | cut -f 2- | xargs -I{} ls -Glath --si {}

Giving output like:

-rw-r--r-- 1 es 23K Jul 10  2010 ./laptop_0000071.jpg
-rw-r--r-- 1 es 43M Jul 29 19:19 ./work.xcf
-rw-r--r-- 1 es 87K Jul 29 20:11 ./patent_lamps/US Patent 274427 Maxim Lamp Holder.jpg
-rw-r--r-- 1 es 151K Jul 29 20:12 ./patent_lamps/Edison screw-in socket.png
-rw-r--r-- 1 es 50K Jul 29 20:13 ./patent_lamps/1157 Lamp.jpg
-rw-r--r-- 1 es 38K Jul 29 20:14 ./patent_lamps/US06919684-20050719-D00001.png

Where:

  • -I{} causes the occurrence of {} to be replaced by an argument, and newlines are now the argument separators (note the spaces in filenames above).

  • ls -G suppresses printing the group name (waste of space).

  • ls -h --si produces human-readable file sizes (more correct with --si).

  • ls -t sorts by time, which is irrelevant here, but that's what I typically use.

Evgeni Sergeev
  • 1,855
  • 4
  • 23
  • 37
17

I have a simple solution that works for both FreeBSD (OS X) and Linux:

find . -type f -exec ls -t {} +
Alex Shchur
  • 279
  • 2
  • 3
  • 1
    This works perfectly - should be correct answer, or at least higher rated! – digitaltoast Jun 14 '19 at 08:13
  • `find . -type f -exec ls -lat {} +` gives you a better picture with date and time showing if somebody needs this format – jturi Feb 11 '20 at 14:21
  • 2
    This should be the best answer. Simple, easy to understand. The key is the plus sign in the end. This allows the `find` to call `ls` once only. – HKTonyLee Mar 24 '22 at 23:35
  • Only works reliably as long as all filenames returned by `find .` fit on one command line, subject to the platform's [`ARG_MAX`](https://www.in-ulm.de/~mascheck/various/argmax/). – TheDudeAbides Feb 28 '23 at 21:49
13

You do only need ls

You could do find /wherever/your/files/hide -type f -exec ls -1rt "{}" +; as stated above,

or

ls -1rt `find /wherever/your/file/hides -type f`
skippy1910
  • 147
  • 1
  • 2
  • 4
    If there are a lot of files, this fails with 'Argument list too long' on the ls. Maybe recook to use xargs? – occulus Sep 03 '12 at 08:41
  • 4
    But if `xargs` calls `ls` multiple times, the sort will be broken. – Aaron D. Marasco Nov 02 '15 at 17:48
  • This fails for files with spaces in their names. Any advice? – user74094 Jan 19 '18 at 14:16
  • Just stumbled upon this answer and it was exactly what I needed in a similar situation. Question: what does the `+;` at the end do? It seems to give the same result without the `;` however it does not work without the `+` ? – RocketNuts Mar 21 '19 at 23:36
  • This is just the same as another answer posted 8 months before, except for the part about using "ls -1rt \`find …\`", which is broken – Clément Apr 13 '19 at 19:16
7

OS X variant of @user195696's answer:

  1. With timestamp:

     find . -type f -exec stat -f "%Sm %N" -t "%Y%y%m%d%H%M" {} \; | sort -r
    
  2. Without timestamp:

     find . -type f -exec stat -f "%Sm %N" -t "%Y%y%m%d%H%M" {} \; | sort -r | awk -F' ' '{ print substr($0, length($1) + 2) }'
    

    or, alternatively,

     find . -type f -exec stat -f "%Sm %N" -t "%Y%y%m%d%H%M" {} \; | sort -r | cut -d ' ' -f2-
    

As with many of the other answers, this will fail for filenames that contain newline(s), but it should work for filenames that contain (horizontal) whitespace.

user9399
  • 171
  • 1
  • 2
3

Here is a clean and robust way for sort | head by date:

Using ls -l for pretty print

find . ! -type d -printf "%T@ %p\0" |
    sort -zrn |
    head -zn 10 |
    sed -z 's/^[0-9.]\+ //' |
    xargs -0 ls -lt

As a function:

findByDate() {
    local humansize=''
    [ "$1" = "-h" ] && humansize='h' && shift
    find . ${2:-! -type d} -printf "%T@ %p\0" |
        sort -zrn |
        head -zn ${1:--0} |
        sed -z 's/^[0-9.]\+ //' |
        xargs -0 ls -dlt${humansize}
}

This could by run with one or two argument, or even without:

Usage: findByDate [-h] [lines] [find options]

Sample:

findByDate

Will list all non directories sorted by date. Nota:

Even on big filesystem tree, as xargs recieve already sorted list, the file order stay correct, even if ls must be run many times.

findByDate -h 12

Will list 12 more recents non directories sorted by date, with size printed in human readable form

findByDate 42 '-type l'

Will list 42 more recents symlinks

findByDate -0 '( -type l -o -type b -o -type s -o -type c )'

Will list all symlinks, block devices, sockets and characters devices, sorted by date.

Inverting order

Replacing head by tail and change switch of sort and ls:

findByDate() {
    local humansize=''
    [ "$1" = "-h" ] && humansize='h' && shift
    find . ${2:-! -type d} -printf "%T@ %p\0" |
        sort -zn |
        tail -zn ${1:-+0} |
        sed -z 's/^[0-9.]\+ //' |
        xargs -0 ls -dltr${humansize}
}

Same function, same usage:

Usage: findByDate [-h] [lines] [find options]
2

Try:

find '$dir' -name '$str'\* -print | xargs ls -tl | head -10

But it's also useful to filter data by -mmin/-mtime and -type.

Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
2

I found that this gets the job done on Mac OS X (and generic enough to work on other Unixen as well):

find . -type f -ls | awk '{print $(NF-3), $(NF-2), $(NF-1), $NF}' | sort
Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
Bryan Petty
  • 121
  • 3
  • 3
    Sadly, this prints out localized month names on my Croatian setup, making sort incorrect. – Ivan Vučica Apr 15 '13 at 20:19
  • [user195696's answer](http://superuser.com/questions/294161/unix-linux-find-and-sort-by-date-modified/546900#546900) works for the Croatian setup (and others). – Peter Mortensen Dec 27 '16 at 14:13
1

Use:

find . -type f -mtime 0 -printf "[%TD %TI:%TM%Tp] %s %p\n" | sort -n | awk '{
    hum[1024**4]="TB"; hum[1024**3]="GB"; hum[1024**2]="MB"; hum[1024]="KB"; hum[0]="B";
    for (x=1024**4; x>=1024; x/=1024){
    if ($3>=x) { printf $1" "$2"\t%7.2f %s\t%s\n",$3/x,hum[x],$4;break }
    }}';

This command will sort files by modified date.

And display out like:

[12/05/13 03:10PM] 1.75 MB ./file.text
[12/06/13 11:52PM] 2.90 MB ./file2.mp4
[12/07/13 04:11PM] 4.88 MB ./file3.mp4
[12/07/13 09:17PM] 4.74 MB ./test.apk
Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
Akash
  • 11
  • 1
  • I improved this sript to handle whitespace in filenames, see http://superuser.com/a/777007/134532 – jan Jul 04 '14 at 14:59
1

If your find selection is very simple, you might be able to do without it, and just use ls:

ls -1 *.cc # -r -t optional
djc
  • 201
  • 1
  • 5
0

You can use stat on BSD and Linux (not on POSIX) in this fashion:

$ stat -f "%m%t%N" /[the dir]/* | sort -rn | cut -f2-

If you want to limit the number:

$ stat -f "%m%t%N" /[the dir]/* | sort -rn | head -[the number] | cut -f2-
drewk
  • 1,194
  • 7
  • 13
0

I don't think find has any options to modify the output ordering. -mtime and -mmin will let you restrict the results to files that have been modified within a certain time window, but the output won't be sorted -- you'll have to do that yourself. GNU find has a -printf option that, among other things, will let you print the modification time of each file found (format strings %t or %Tk) ; that might help you sort the find output the way you wish.

Jim Lewis
  • 199
  • 1
  • 5
0

If you want to order all PNG files by time in $PWD:

This simple one-liner gives all the flexibility of regexp on find and on ls.

find $PWD -name "*.png" -print0 | xargs -0 ls -laht | less
Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
0

I improved Akashs answer by making the script handling whitespace in filenames correctly:

find . -type f -mtime 0 -printf ";[%TD %TI:%TM%Tp];%s;%p\n" | sort -n | awk -F ";" '{
    hum[1024**4]="TB"; hum[1024**3]="GB"; hum[1024**2]="MB"; hum[1024]="KB"; hum[0]="B";
    for (x=1024**4; x>=1024; x/=1024){
    if ($3>=x) { printf $1" "$2"\t%7.2f %s\t%s\n",$3/x,hum[x],$4;break }
    }}';
jan
  • 225
  • 1
  • 3
  • 7
-1

If you just want to get a full path of each item you can write down like this.

 find FIND_ROOT -maxdepth 1 -type f -printf "%T@ %p\n" | sort -nr | head -10 | cut -d ' ' -f 2

Where
-printf "%T@ %p\n" for giving sorting criteria (date),
'sort -nr' for sorting by date,
head -10 for listing top 10 results,
cut -d ' ' -f 2 for cutting the leading timestamp on each line.

David Jung
  • 111
  • 2
-3

I have a simple solution.

After cd to a directory, use

find . -iname "*" -ls

bertieb
  • 7,344
  • 36
  • 42
  • 54
sing
  • 1
  • 1