如何查找仅包含 X 天之前的文件的目录?

如何查找仅包含 X 天之前的文件的目录?

我需要删除仅包含早于 X 天的文件(创建/修改)的目录。

我尝试过这个,但它只会显示今天的文件,而不是递归......

for d in *; do   
  find "$d" -mindepth 1 -mtime -180 -print -quit | | grep -q . ||     
  echo rm -rf "$d";
done

答案1

你要 ”删除仅包含 X 天之前的文件的目录”。

这是处理它的一种方法:

  • 依次对于每个目录,从叶节点开始:
  • 统计其子目录的数量;如果非零则跳过
  • 统计其非目录项的数量;如果为零则跳过
  • 计算符合您的标准(年龄)的文件数量
  • 如果两个文件数相同则删除该目录

此解决方案需要 GNU或能够理解、和 的find替代版本。-mindepth-maxdepthprintf

此特定示例将您设置X180(天)。

days=180
find ./* -depth -type d -exec sh -c '
    [ -z "'"$days"'" ] && exit 1
    printf "Considering directory: %s\n" "$1"

    dirs=$(find "$1" -mindepth 1 -maxdepth 1 -type d -print | xargs)
    if [ -n "$dirs" ]
    then
        printf "Found child directories: (%s)\n" "$dirs"
        exit 0
    fi

    all=$(find "$1" -maxdepth 1 ! -type d -printf x | wc -c)
    got=$(find "$1" -maxdepth 1 -type f -mtime +'"$days"' -printf x | wc -c)
    printf "Found %d item(s) and matched %d\n" $all $got

    if [ $all -gt 0 ] && [ $all -eq $got ]
    then
        printf "Delete directory: %s\n" "$1"
        : rm -rf "$1"
    fi
' _ {} \;

围绕变量的两种用途的看似奇怪的引用$days是为了确保虽然脚本的其余部分用单引号括起来,但变量用双引号括起来,以便可以确定其值。它可能更容易理解为'..start of script..' "$variable" '..remainder of script..',只不过一种引号的结尾与另一种引号的开头之间没有空格。

printf如果您希望代码静默运行,请删除这些语句。当您准备好执行删除操作时,请删除:前面的冒号 ( )。rm -rf

答案2

假设您想要不包含(在任何级别)不早于 180 天的非目录文件的最浅目录(如果两者./a/b都不./a包含最近的文件,则仅报告./a其中./a/b的一部分),这将是一种变体在仅列出不包含文件的最浅目录,一直向下,所以你可以使用与我的答案如果在 GNU 系统上:

find . -type d -print0 -o -mtime -180 -printf 'f/%h\0' |
  LC_ALL=C sort -zru |
  LC_ALL=C awk -F/ -v RS='\0' '
    function parent(path) {
      sub("/[^/]*$", "", path)
      return path
    }
    $1 == "f" {
      sep = path = ""
      for (i = 2; i <= NF; i++) {
        black[path = path sep $i]
        sep = FS
      }
      next
    }
    ! ($0 in black) && ($0 == "." || parent($0) in black)'

哪里不是把目录涂成黑色任何非目录文件,我们将那些有的文件涂成黑色任何不超过 180 天的非目录文件

或者当找到最近的文件时从数组中删除目录的变体:

find . -type d -printf '%p/\0' -o -mtime -180 -printf '%h/f\0' |
  LC_ALL=C sort -zu |
  LC_ALL=C awk -F/ -v RS='\0' '
    function parent(path) {
      sub("[^/]+/?$", "", path)
      return path
    }
    /\/$/ {dir[$0]; next}
    {
      path = ""
      for (i = 1; i <= NF; i++)
        delete dir[path = path $i FS]
    }
    END {
      for (path in dir) if (! (parent(path) in dir)) print path
    }'

如果你想要两者./a./a/b以上(不仅仅是最浅的),它就变得更简单:

find . -type d -printf '%p/\0' -o -mtime -180 -printf '%h/f\0' |
  LC_ALL=C sort -zu |
  LC_ALL=C awk -F/ -v RS='\0' '
    /\/$/ {dir[$0]; next}
    {
      path = ""
      for (i = 1; i <= NF; i++)
        delete dir[path = path $i FS]
    }
    END {
      for (path in dir) print path
    }'

如果你想删除它们,添加一个-v ORS='\0'toawk以 NUL 分隔打印它们,然后通过管道传递到xargs -r0 rm -rf,但要注意如果./返回(如果当前目录中没有任何最近的文件),大多数rm实现将拒绝执行任何操作。

由于这些方法find仅运行并遍历整个目录树一次,因此它们比抓取每个目录的内容以查找最近文件的更简单的方法更有效。

一种效率较低的方法是zsh

print -rC1 -- **/*(ND/^e['()(($#)) $REPLY/**/*(NDm-180Y1^/)'])

(不包括.,但您可以使用将其添加到列表中{.,**/*}(...)

如果您只想考虑当前目录的子目录,那么该方法会变得更有效(因为上面Y1在第一个匹配处停止)并且更短/更简单:

print -rC1 -- *(ND/^e['()(($#)) $REPLY/**/*(NDm-180Y1^/)'])

.*1 作为一种解决方法,因为在某些 shell 中的扩展包括.和 ,..并且对于 来说可能会带来灾难性的后果rm -rf .*。使用 启用的(没有该缺陷的 shell)rm的内置函数将接受并清空当前工作目录。zshzmodload zsh/filesrm -rf ./

相关内容