5

To find files older than the newest 8 I'm using:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -rz | sed -z 1,8d

The -printf '%T@ %p\0' command applies a last modified timestamp (expressed as number of seconds since Jan. 1, 1970, 00:00 GMT, with fractional part) to the beginning of each zero-terminated filename matched, something like:

1597765267.7628475560 ./fileName2.txt1597765264.0267179360 ./fileName1.txt

In order to delete these oldest files by piping to xargs -0 gio trash, I need to remove the timestamps. So I add another sed command to do this:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -rz | sed -z 1,8d | sed -z "s/^[0-9]*.[0-9]* //g"

Now I have the correct output, but is there a better more efficient way?


Based on @Quasímodo's answer, I tried to simplify further by using the sed delete pattern format (as I'm not actually substituting anything), but I got no output:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -rnz | sed -z "1,8d; /^[0-9]*\.[0-9]* /d"

Any suggestions appreciated.

Conclusion (from multiple responses):

find $targetPath -maxdepth 1 -type f -printf '%T@ %p\0' | sort -rnz | sed -z "1,${retain}d; s/^[^ ]* //"

Where:
$targetPath, Directory to find files, e.g. current directory '.'
${retain}, Number of newest files to retain, e.g. 8

5
  • @Quasímodo, d is for delete isn't it, as in the 1,8d? I'm not actually substituting the pattern for anything, so I just want to delete it, can we chat? Nov 23, 2020 at 10:41
  • 1
    I would skip sed and do something like -printf '%T@\t%p\0' | sort -rnz | tail -zn +9 | cut -zf2-
    – user986805
    Nov 23, 2020 at 10:51
  • @terdon, I want to retain the most recent 8 and list all other older files for pipe to trash. My original command plus Quasímodo's corrections and suggestions works nicely. Nov 23, 2020 at 12:36
  • ... I would skip bash and use zsh extended globs ex. rm -- *(.Dom[8,-1]) (the D is included for compatibility with find, which doesn't treat hidden files specially) Nov 23, 2020 at 12:46
  • 1
    Sigh, sorry @Broadsworde I was being an idiot. I had made a mistake when creating my test files, you're quite right this does indeed keep the 8 newest and not the other way around as I suggested in my previous comment. My (edited) answer should work for you.
    – terdon
    Nov 23, 2020 at 16:07

3 Answers 3

5

Since you control the output, it is very easy to remove the timestamps. You know that each \0-delineated record will start with the timestamp and then a space. So all you need to do is remove all non-space characters until the first space:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | 
    sort -rz | sed -z '1,8d; s/^[^ ]* //'

However, you probably want numeric sort and you may as well only sort on the first field, no need to also sort on the file name. So, this will give you the 8 oldest files:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | 
    sort -rznk1,1 | sed -z '1,8d; s/^[^ ]* //'
3
  • is there an advantage to using the "until not space" substitute pattern; sed -z "s/^[^ ]* //", vs a generic, "any number of characters until space" substitute pattern; sed -z "s/^.* //", ? Nov 26, 2020 at 16:33
  • 1
    @Broadsworde yes, sed regular expressions are "greedy", so if you use /^.* /, that will match everything until the last space of the line. Since I want to stop at the first space of the line, I tell it to match 0 or more non-space characters ([^ ]*) until it finds a space. If I were to use s/^[^ ]* //', that would fail for file names containing spaces.
    – terdon
    Nov 26, 2020 at 17:07
  • Thank you kindly, that's a gold nugget of knowledge, much appreciated. Nov 27, 2020 at 14:17
4

Convert the two Sed processes into a single one.

sort -zrn | sed -z "1,8d; s/^[0-9]*\.[0-9]* //"

Corrections applied:

  • Do a numerical Sort.
  • . matches any character, it should be \. in the regular expression.
  • g substitutes all matches in a record. You only need a single substitution (namely, removing the time stamp) from each record, so remove that g. This modification also improves performance.

Your attempt sed -z "1,8d; /^[0-9]*\.[0-9]* /d" fails because /^[0-9]*\.[0-9]* /d deletes every line matching the regex. This is different from s/^[0-9]*\.[0-9]* //, that deletes the string matching the regex.

0
3

You can use your find command with GNU awk:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -zrn | awk 'BEGIN{RS=ORS="\0"} NR>8 {sub(/[^ ]+ /, ""); print}'

Here:

  • We set the input record separator (RS) and the output record separator (ORS) to \0.

  • For each record from the 8th record onwards (NR>8), we replace everything up to the first space, including the first space, with an empty string (sub(/[^ ]+ /, "")) and print the record (print). Thanks @Quasimodo for suggesting improvements on the above command.

Example:

$ touch {01..15}.txt
$ ls
01.txt  02.txt  03.txt  04.txt  05.txt  06.txt  07.txt  08.txt  09.txt  10.txt  11.txt  12.txt  13.txt  14.txt  15.txt
$ find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -zrn | awk 'BEGIN{RS=ORS="\0"} NR>8 {sub(/[^ ]+ /, ""); print}'
./07.txt./06.txt./05.txt./04.txt./03.txt./02.txt./01.txt
0

You must log in to answer this question.

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