I'm looking for a shell one-liner to find the oldest file in a directory tree.
8 Answers
This works (updated to incorporate Daniel Andersson's suggestion):
find -type f -printf '%T+ %p\n' | sort | head -n 1
- 2,560
This one's a little more portable and because it doesn't rely on the GNU find extension -printf, so it works on BSD / OS X as well:
find . -type f -print0 | xargs -0 ls -ltr | head -n 1
The only downside here is that it's somewhat limited to the size of ARG_MAX (which should be irrelevant for most newer kernels). So, if there are more than getconf ARG_MAX characters returned (262,144 on my system), it doesn't give you the correct result. It's also not POSIX-compliant because -print0 and xargs -0 isn't.
Some more solutions to this problem are outlined here: How can I find the latest (newest, earliest, oldest) file in a directory? – Greg's Wiki
- 235,242
The following commands commands are guaranteed to work with any kind of strange file names:
find -type f -printf "%T+ %p\0" | sort -z | grep -zom 1 ".*" | cat
find -type f -printf "%T@ %T+ %p\0" | \
sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //'
stat -c "%y %n" "$(find -type f -printf "%T@ %p\0" | \
sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //')"
Using a null byte (\0) instead of a linefeed character (\n) makes sure the output of find will still be understandable in case one of the file names contains a linefeed character.
The -z switch makes both sort and grep interpret only null bytes as end-of-line characters. Since there's no such switch for head, we use grep -m 1 instead (only one occurrence).
The commands are ordered by execution time (measured on my machine).
The first command will be the slowest since it has to convert every file's mtime into a human readable format first and then sort those strings. Piping to cat avoids coloring the output.
The second command is slightly faster. While it still performs the date conversion, numerically sorting (
sort -n) the seconds elapsed since Unix epoch is a little quicker. sed deletes the seconds since Unix epoch.The last command does no conversion at all and should be significantly faster than the first two. The find command itself will not display the mtime of the oldest file, so stat is needed.
- 50,701
Although the accepted answer and others here do the job, if you have a very large tree, all of them will sort the whole bunch of files.
Better would be if we could just list them and keep track of the oldest, without the need to sort at all.
That's why I came up with this alternative solution (has some bugs, see edits):
ls -lRU $PWD/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($1,0,1)=="/") { pat=substr($1,0,length($0)-1)"/"; }; if( $6 != "") {if ( $6 < oldd ) { oldd=$6; oldf=pat$8; }; print $6, pat$8; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
I hope it might be of any help, even if the question is a bit old.
Edit 1: this changes allow parsing files and directories with spaces. It is fast enough to issue it in the root / and find the oldest file ever (modification time).
ls -lRU --time-style=long-iso "$PWD"/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($0,0,1)=="/") { pat=substr($0,0,length($0)-1)"/"; $6="" }; if( $6 ~ /^[0-9]+$/) {if ( $6 < oldd ) { oldd=$6; oldf=$8; for(i=9; i<=NF; i++) oldf=oldf $i; oldf=pat oldf; }; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Command explained:
- ls -lRU --time-style=long-iso "$PWD"/* lists all files (*), long format (l), recursively (R), without sorting (U) to be fast, and pipe it to awk
- Awk then BEGIN by zeroing the counter (optional to this question) and setting the oldest date oldd to be today, format YearMonthDay.
- The main loop first
- Grabs the 6th field, the date, format Year-Month-Day, and change it to YearMonthDay (if your ls doesn't output this way, you may need to fine tune it).
- Using recursive, there will be header lines for all directories, in the form of /directory/here:. Grab this line into pat variable. (substituting the last ":" to a "/"). And sets $6 to nothing to avoid using the header line as a valid file line.
- if field $6 has a valid number, it's a date. Compare it with the old date oldd.
- Is it older? Then save the new values for old date oldd and old filename oldf. BTW, oldf is not only 8th field, but from 8th to the end. That's why a loop to concatenate from 8th to the NF (end).
- Count advances by one
- END by printing the result
Running it:
$ time ls -lRU "$PWD"/* | awk ... etc.
Oldest date: 19691231
File: /home/.../.../backupold/.../EXAMPLES/how-to-program.txt
Total compared: 111438
real 0m1.135s
user 0m0.872s
sys 0m0.760s
EDIT 2: Same concept, better solution using find which can look at the access time (%A, or use %T with the first printf for modification time or %C for status change instead).
find . -wholename "*" -type f -printf "%AY%Am%Ad %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
EDIT 3: The command below uses modification time (%T) and also prints incremental progress as it finds older and older files, which is useful when you have some incorrect timestamps (like 1970-01-01):
find . -wholename "*" -type f -printf "%TY%Tm%Td %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; print oldd " " oldf; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
- 2,394
- 2,125
Please use ls - the man page tells you how to order the directory.
ls -clt | head -n 2
The -n 2 is so you dont get the "total" in the output. If you only want the name of the file.
ls -t | head -n 1
And if you need the list in the normal order (getting the newest file)
ls -tr | head -n 1
Much easier than using find, much faster, and more robust - dont have to worry about file naming formats. It should work on nearly all systems too.
- 165
It seems that by "oldest" most people have assumed that you meant "oldest modification time." That's probably corrected, according to the most strict interpretation of "oldest", but in case you wanted the one with the oldest access time, I would modify the best answer thus:
find -type f -printf '%A+ %p\n' | sort | head -n 1
Notice the %A+.
- 135
set $(find /search/dirname -type f -printf '%T+ %h/%f\n' | sort | head -n 1) && echo $2
find ./search/dirname -type f -printf '%T+ %h/%f\n'prints dates and file names in two columns.sort | head -n1keeps the line corresponding to the oldest file.echo $2displays the second column, i.e. the file name.
- 9,861
- 11