1

Beginner here: I want to sync pictures to a folder but want them to be recent. That's why I want them to be deleted if they are older then 7 days.

Current code for it:

find ./recentpicturesdirectory -mtime -7 -type f -delete 

Now I need an exception because there should always be a minimum of 20 pictures in the folder.

I did some research but can't figure it out. Maybe use something else then the find command?

First sad try (told you I'm a beginner)

SIZE=find recentpictesfolder -type f | wc -l

find ./recentpicturesdirectory -mtime -7 -type f \(-iname ".*" ! **if files part of -> .. i dont know ...**$SIZE ) -delete 

4 Answers 4

1

So we want to get the (regular) files that have mtime more than 7 days ago AND are not within the 20 newest files.

One way to do that is to construct a list of files along with their modification times and a flag indicating whether the file is mtime +7 or not. We can then sort the list by mtime, and take all but the 20 most recent1. Finally, iterate over the resulting list and delete only those that meet the age threshold:

find ./recentpicturesdirectory -type f \( -mtime +7 -printf 'Y\t' -o -printf 'N\t' \) -printf '%A@\t%p\0' |
  sort -zk2,2 | head -zn -20 | while read -r -d '' flag _ file; do \
    case "$flag" in 
      'Y') echo rm "$file" 
           ;; 
        *) echo "skipping $file (too new)"
           ;; 
    esac
  done

By using \0 termination and adding the -z flag to the sort and head (or tail) commands, we can handle any legal filenames without choking on whitespace.

The echo is added for testing purposes; remove it once you're certain that the desired files are selected for deletion.

1 If we sort in ascending order of mtime (i.e. old to new), we can use head -n -20 to select all but the last 20; alternatively we could sort in descending order of mtime (new to old) and use tail -n +21


If you have zsh, then you can do it all with glob qualifiers, I think:

rm ./recentpicturesdirectory/**/*(.^om[1,20]^m+7)

where

  • **/* matches recursively (equivalent of bash globstar)
  • (.) match regular files only
  • om[1,20] lists results in ascending order of mtime, and select the first 20
  • m+7 match only files with mtime > 7 days
  • ^ invert everything that follows

so the logic is

regular files NOT (in 20 most recent by mtime OR NOT mtime +7 days)

which (by application of de Morgan's rules) is equivalent to

regular files (NOT in 20 most recent by mtime) AND (mtime +7 days)

Please do a trial run first though e.g.

print -rl ./recentpicturesdirectory/**/*(.^om[1,20]^m+7)
1

Here is a simple script to do this:

#!/bin/bash

IMGSPATH="/path/to/somewhere"

# count of all files within your directory
count=$(find $IMGSPATH -type f | wc -l)

# remove the ones older than of 7 days
while [ "$count" -gt "20" ]
do 
 find $IMGSPATH -type f -mtime +7 -print -delete -quit
 count=$((--count))
done
  1. First we count all files within your desired directory.
  2. While the number of files within that directory is greater than of "20", then do:
    • Find the first file older than of "7" days.
    • Remove it
    • Decrease the count of available files
    • Do "#2" again ...

To test it:

mkdir /tmp/lab
cd /lab
touch {1..40}
touch -d "10 days ago" {1..20}

Save the script and run it for /tmp/lab now files 1 to 20 should be deleted, run:

touch -d "10 days ago" {21..35}

run the script again, nothing will get removed cause you don't have more than of 20 files whatever they're old or not.

0

My underlying approach is the same as in steeldriver’s answer but I’m going to keep it a little simpler. The following commands will print a list of files in recentpicturesdirectory omitting the retain_count (20) youngest and all younger than retain_younger_days days (7).

find recentpicturesdirectory -type f -printf '%T@ %p\0' |
sort --zero-terminated --reverse --numeric-sort --field-separator=' ' --key 1,1 |
gawk -F ' ' -v RS='\0' -v ORS='\0' -v retain_count=20 -v retain_younger_days=7 \
  'BEGIN{ maxage = systime() - retain_younger_days * 24 * 3600; }
  (NR > retain_count) && (int($1) < maxage) { print(substr($0, length($1) + 2)); }'

Since the output is null-terminated you may want to pipe it through tr '\0' '\n' to display it human-readably.

Explanation

  • find recentpicturesdirectory -type f -printf '%T@ %p\0'

    Select the (nested) entries of recentpicturesdirectory which are regular files (-type -f) and print their last modification time and path terminated by the null character (-printf '%T@ %p\0').

  • sort --zero-terminated --reverse --numeric-sort --field-separator=' ' --key 1,1

    Sort records terminated by the null character (-z/--zero-terminated) based on the first field (-k/--key 1,1) with the space character field separator (-t/--field-separator=' ') interpreted as decimal numbers (-n/--numeric-sort) in descending order (-r/--reverse).

  • gawk -F ' ' -v RS='\0' -v ORS='\0' -v retain_count=20 -v retain_younger_days=7

    Run a GNU AWK program with the space character field separator (-F ' '), the null character input and output record separator (-v RS='\0' -v ORS='\0') and the variables retain_count and retain_younger_days set to 20 and 7 respectively.

  • BEGIN{ maxage = systime() - retain_younger_days * 24 * 3600; }

    At the begin of the program set maxage to the current system time in seconds minus the value of retain_younger_days converted from days to seconds.

  • (NR > retain_count) && (int($1) < maxage)

    Select record numbers above retain_count and a value in the first column below maxage.

  • print(substr($0, length($1) + 2))

    Print the current record omitting the first field and the subsequent field separator, i. e. the path name after the modification time.

How to remove the printed files

If you confirmed that you want to remove the listed files you can pipe the list to:

xargs -r0 -- rm --
0

I was looking for a solution to this issue today and found this and other like posts with either complicated solutions or simple solutions that miss the mark. I came up with the following.

ls -1t ${path} | tail -n +21 | xargs -I{} find {} -type f -mtime +7 | xargs -d '\n' -r rm -f

Explanation

  • ls -1t ${path}

    This lists the files matching the path with one file per line and sorts by modification time, newest first.

    The ${path} is the path to the files, but should match the specific files you want. If you need more advanced pattern matching, you can pipe to something like egrep and use RegEx to match the files you want included.

    For example, the path I use for web site backups is something g like /backups/*_web_site_1_backup.tgz to ensure only those backup files are output.

    If you have multiple files to need to prune like this, then I suggest using a for loop with all your paths. Otherwise the x number of newest files being kept will be a mixture of the different files rather than the x number of newest files for each.

  • tail -n +21

    This will output the results of the previous command starting at the line number you specify. So if you want to keep the first 20 files, the number specified should be 21 to output file names starting at the 21st.

  • xargs -I{} find {} -type f -mtime +7

    The xargs command is being used because find won't accept the path via a pipe. Normally xargs places the content piped to it at the end of the command, but find requires the search path to be its first argument. The -I switch lets you specify a string to identify where you want the piped content placed in the command.

    The -mtime +7 specifies that anything over 7 days will be matched. Since the first 20 were excluded, it will only match against files starting at the 21st.

  • xargs -d '\n' -r rm -f

    This deletes the files matched by the find command.

    The -d switch followed by \n tells xargs to use new line as a delimiter so it will pass as many results to the command as possible rather than run the command for each file.

    The -r switch tells xargs to not run the command if there is no data.

This is an example using a for loop.

for file_to_check in /backups/*_web_site_1_backup.tgz /backups/*_web_site_2_backup.tgz
 do
     ls -1t ${file_to_check} | tail -n +21 | xargs -I{} find {} -type f -mtime +7 | xargs -d '\n' -r rm -f
done

You must log in to answer this question.

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