How to delete all files in ~/Downloads that have not been touched, added, or opened in the last 30 days?

Viewed 77

I'm trying to create an automator workflow or application that, when activated, deletes all the files and subfolders in my Downloads folder that have not been created, modified, added, opened, or accessed in any way in the last 30 days.

I tried filtering like this:

screenshot of automator

But that doesn't really do the job like I want it. First of all, there's no option to filter by "date added", which I would really like. Secondly, I would prefer it to prioritize a subfolder over that subfolder's contents. For example, I have a folder which I added today, but the file inside that folder has a "date added" of much longer ago. My preference would be that that folder, including its contents, are ignored and therefor not deleted.

screenshot of dates of example folder and files

Then I read in another Stack Overflow thread (or it was at least some Stack Exchange site) that someone recommended to use a bash script instead. Something like this for example:

$ find "$HOME/Downloads" -type fd -mtime +30d -atime +30d -iname '*.*'

But even that doesn't seem to filter out the exact items that I want to filter out.

So just to be clear, I want to delete everything in my Downloads folder that has not been added, opened, created or modified in the last 30 days. And if there's any subtree where any of the folders or files within that subtree has been added, opened, created or modified within the last 30 days, then I would like that entire subtree to be ignored and left alone. Can anyone help me out here?

2 Answers

This is another approach if you don't want to use find. Works with folders, files. comment out the rm -rf to confirm.

#!/bin/bash
compareDate=$(date -d "30 days ago" '+%Y%m%d')
for f in ~/Downloads/*;
do
    fileDate=$(date -r "$f" -u "+%Y%m%d")
    if [ ! "$fileDate" -gt "$compareDate" ];then
        echo Deleting - "$f";
        rm -rf  "$f"
    else 
        echo Keeping - "$f"
    fi
done

You can do so with find and the -newerXY option (which you negate) where XY is equal to mt for modification time, at for access time and ct for creation time. You simply pass -delete to remove the matching filenames. You can do:

d=$(date -d "30 days ago" '+%F %T')     # get date and time 30 days ago
find ~/Downloads -type f ! -newermt "$d" ! -newerat "$d" ! -newerct "$d" -delete

(the order of the options is important as they are evaluated as an expression, if you put -delete first it will delete all files under the ~/Download path as there is nothing to modify the list of files before -delete is encountered)

Note: test without -delete to make sure it returns the list you expect it to and then add the option back in to actually remove the files.


Thoughts On Change of Question to If Any File is Newer in Mod, Access or Change -- Keep all in that Directory

After your edit, where any one file in a subdirectory will prevent removal of any files in the subdirectory, that will prevent a single call to find from being helpful since find processes a single file at a time without knowledge of how tests on other files went.

Here my thought is more to loop over the directories under ~/Downloads one at a time relying on globstar being set. You will change to your "$HOME" directory (in the script) so the paths generated by the **/ search will be relative to "$HOME" without extraneous other path components for /home/user prepended to them.

Create a short function that loops over each file in the directory being processed, and if any one file is newer in modification, access or change, do nothing with that directory, all files are save.

A quick implementation using stat to using date and mod, access and change times in seconds since epoch, you could do:

#!/bin/bash

shopt -s globstar     # endable globstar globbing

dt=$(date -d "30 days ago" '+%s')   # 30 days ago in seconds since epoch

cd "$HOME" || exit 1  # change to home directory path globbing to Downloads
dld="Downloads"       # set Downloads varabile 

# function returns 0 if no files in dir with access or mod time in 30 days
# returns 1 otherwise (don't remove)
nonenewerthan30 () {
  local dir="$1"
  [ -d "$dir" ] || return 1   # validate it is a dir
  for f in "$dir"/*; do       # loop over files in dir
    [ -d "$f" ] && continue   # skip any directories in dir
    [ $(stat -c %X "$f") -gt "$dt" ] && return 1  # mod time since epoch
    [ $(stat -c %Y "$f") -gt "$dt" ] && return 1  # access time since epoch
    [ $(stat -c %Z "$f") -gt "$dt" ] && return 1  # change time since epoch
  done
  
  return 0    # directory can be removed.
}


for d in "$dld"/**/; do               # loop Downloads and all subdirs
  d="${d%/}"                          # remove trailing '/'
  [ "$d" = "$dld" ] && continue       # skip Downloads until subs processed
  printf "\nprocessing: %s\n" "$d"
  nonenewerthan30 "$d" && {           # call func, on 0 return, remove sub
    echo "  can remove $d"
    # rm -r "$d"           # uncomment after your verify behavior
  }
done

Currently it skips processing the files in Downloads until all the subdirectories are done. You will need to keep track if files are retained at any level to know whether or not removing them from Downloads is even an options. Adding that logic I leave to you.

Related