Search code examples
bashunixserversynology

"find -mtime +5 | xargs rm -rf" destroying whole directories, even when files newer than 5 days exist


I have a series of folders, subfolders and files like this :

year_folder
    month1_folder
       day1_folder
           filea, fileb
       day2_folder
           filea
    month2_folder

I want to delete the folders and files older than X days. I have tried

find /c/Documents/year_folder -mtime +5 | xargs rm -rf

This command line works perfectly on my test folders (locally on my computer). But when I run the script on synology, somehow it deletes the whole year_folder.

Unfortunately I do not know how to test my script on the server of synology to understand what I am doing wrong.


Solution

  • Using GNU Extensions

    Split this into two pieces:

    • Delete files (only!) older than five days

      # for GNU find; see below for POSIX
      find "$root" -type f -mtime +5 -delete
      
    • Delete empty directories

      # for GNU find; see below for POSIX
      find "$root" -depth -type d -empty -delete
      

    When you use rm -rf, you're deleting the entire directory when the directory itself hasn't been updated in five days. However, if you create or modify a/b/c, that doesn't update the modification time of a (or, in the case of modifications that don't require the directory itself to be updated, not even that of a/b) -- thus, your "modification time older than five days" rule is destructive when you apply it recursively.

    The only caveat to the above is that it may not delete multiple layers of empty directories at a run -- that is, if a/b/c is empty, and a/b is empty other than c, then only c may be deleted on the first run, and it may require another invocation before a/b is removed as well.


    Supporting Baseline POSIX

    POSIX find doesn't support -delete. Thus, the first command becomes:

    find "$root" -type f -mtime +5 -exec rm -rf -- {} +
    

    Similarly, it doesn't support -empty. Because rmdir will fail when passed a non-empty directory, however, it's easy enough to just let those instances referring to non-empty directories fail:

    find "$root" -depth -type d -exec rmdir -- {} +
    

    If you aren't comfortable doing that, then things get stickier. An implementation that uses a shell to test whether each directory is empty may look like:

    find "$root" -depth -type d -exec sh -c '
      rmdir_if_empty() {
        dir=$1
        set -- "$dir"/*                           # replace argument list w/ glob result
        [ "$#" -gt 1 ] && return                  # globbed to multiple results: nonempty
        { [ -e "$1" ] || [ -L "$1" ]; } && return # globbed to one result that exists: nonempty
        rmdir -- "$dir"                           # neither of the above: empty, so delete.
      }
      for arg; do
        rmdir_if_empty "$arg"
      done
    ' _ {} +