Working with the newest or oldest files


I recently wanted to delete all but the most recent 3 daily backup files. Deleting based on timestamp isn’t safe in this case; if you delete files older than 3 days then you’ll lose all backup files if the backup stops creating new backups for some reason. It’s relatively easy to work with files older or newer than a particular date but it’s less obvious how to deal with the N oldest or newest files, regardless of absolute timestamp, without writing a script.

In the current directory (easy)

ls -t shows newest entries first (files, directories and others), ls -tr shows oldest first. If you are sure there are only regular files then this is the simplest. Don’t use this method if some entries might be directories.

Keep only the newest 5. +6 here means “start at line 6”, thus skipping the first five lines:

ls -t | tail -n +6 | xargs -I{} rm -f {}

Delete only the newest 5:

ls -t | head -n 5 | xargs -I{} rm -f {}

If you’re sure all the filenames are safe (no whitespace or weird characters) you can avoid xargs and use shell expansion instead:

rm -f $(ls -t | tail -n +6)

rm -f $(ls -t | head -n 5)

In several directories or only specific files

As above, but specify one of more directories and a wildcard. Limited by the maximum length of shell arguments (probably around 2 megabytes for bash) as well as the maximum argument count (getconf ARG_MAX). Don’t use if it’s likely that you’ll exceed either limit or some entries might be directories.

Delete all but the most recent 3 tar files in the current directory:

ls -t *.tar | tail -n +4 | xargs -I{} rm -f {}

In many directories, possibly subdirectories

Use find to avoid argument length limits. Use null-termination of each entry to handle weirdly-named files (Linux filenames are byte streams with only / and null characters forbidden so they could have any crap in them).

Firstly, generate a stream of (only!) files with their modification timestamps; timestamp is first, then a TAB, then the filename, then a null (this will look weird in the terminal):

find /some/path -type f -printf '%T@\t%p\0'

Next, sort by the first field (the timestamp) in reverse order, so newest is first (note the use of -z to use null-terminated records):

find /some/path -type f -printf '%T@\t%p\0' | sort -zrn -k1,1

Cut off the timestamp field leaving only the filename:

find /some/path -type f -printf '%T@\t%p\0' | sort -zrn -k1,1 | cut -zf 2

Now we can use the results. Delete the newest 10 (in xargs note the -0 which is the equivalent of -z in the other tools):

find /some/path -type f -printf '%T@\t%p\0' | sort -zrn -k1,1 | cut -zf 2 | head -zn 10 | xargs -0 rm -f

(Replace rm with echo rm to see what it would actually do.)

Or reverse the sort (remove -r) to delete the oldest 10:

find /some/path -type f -printf '%T@\t%p\0' | sort -zn -k1,1 | cut -zf 2 | head -zn 10 | xargs -0 rm -f

Or swap head for tail and delete all but the newest 10:

find /some/path -type f -printf '%T@\t%p\0' | sort -zrn -k1,1 | cut -zf 2 | tail -zn +11 | xargs -0 rm -f

As an aside, after a recursive file delete like this there may be empty directories left over. Remove all empty directories with:

find /some/path -type d -empty -delete

See also