Find Oldest file in a directory tree

I would use the following command (assuming I had GNU find available):

find $DIR -printf "%T@ %p\n" | sort -n | head -1

The %T is modification time in a custom format; @ is the format specifier for for "seconds since the epoch. The rest should be pretty self-evident. %C would be used if you cared about the "ctime" (the time at which the file's inode was changed in any way, including modifications to the file contents (as with mtime) but also changes to ownership, permissions, link count? etc.

A couple of heuristics for scripting in general:

Any time you're trying to find files with specific characteristics other than matching a simple glob pattern and any of the simple test features like -d (directory) or -r (readable), etc, you almost always want to use the find command. Most of the switches and arguments to find control what find will consider returning (to things like -print, -exec, or -ls).

Any time you're going to compare or manipulate timestamps under UNIX you probably want to use "seconds since the epoch" (this allows simple numeric operations such as sorting or arithmetic adjustment).

So it seems obvious to just traverse the tree printing all the timestamps in a usable form; sort numerically and discard all but the first line. (You could also sort -nr, reversed, and take the last line with tail -1; but then your tail process has to read the entire pipe. In this example the head command can exit and break the pipe to kill to output from the sort command; which is marginally more efficient. Of course all of the expense in this is in the sort command ... it will take most of the CPU time and memory. Meanwhile, of course the find command will expend I/O -- that's inherent in traversing a filesystem tree).

P.S. If you need to convert a "seconds since the epoch" timestamp into a form you can read you can use an expression like:

date --date "$(( $(date +%s) - $TIMESTAMP )) seconds ago"

.. and naturally you can also specify any format you like for that output as you would with any other date

JimD (former Linux Gazette AnswerGuy)

With zsh:

print **/*(Om[1])

Regarding handling path/filenames with possible embedded spaces:

An awk idiom for removing just one or a few fields and printing "the rest of a line" (without an explicit for loop) is to simply set those fields to an empty string and print $0.

So you can do something like:

    awk '{$1=""; print $0}' ...

To effectively just remove the first "word" from each line. (BTW: this example would still reflect the space before $2 --- so you'd want to use BEGIN { OFS="" } if you wanted to avoid the problem.

JimD (former Linux Gazette AnswerGuy)

thanks tim this is a perfect one!!

just found out this bug and wanted to tell you... now i saw you corrected yourself :slight_smile:

big thanks for this one!

Thanks to both of you, this was a huge help for me.

I wanted to add this:

find . -type f -maxdepth 1 -printf "%T@ %p \n" | sort -n -k 1,1 | sed 's/^[0-9]* //' | head -n -3 | xargs -r rm -v

By adding "-maxdepth 1" to the find command, it prevents going through subdirectories, which I didn't want.

Of more use to others though, by using "head -n -3", I can get a list of all but the three newest files, and the "-r" flag on xargs prevents it from running "rm -v" if there are three or fewer files.

However, right now, xargs won't accept files with spaces... There are a number of ways around that, you could use "sed s/ /\\ /g" to replace all spaces with "\ ", which would work... I didn't need it for my purposes, so I left it out.

Anyway, thanks a bunch, I've been on and off wondering how to do this for a while, and you put me on the right path.