bash 上的复杂查找

complicated find on bash

我有以下任务:删除旧的 "builds" 超过 30 天。这个解决方案非常有效:

find $jenkins_jobs -type d -name builds -exec find {} -type d -mtime +30 \; >> $filesToBeDelete
cat $filesToBeDelete | xargs rm -rf

但后来添加了一些条件:仅在我们拥有超过 30 个构建并清理最旧的构建时删除。所以在结果中我们应该保留 30 个最新的构建并删除其余的。

我还发现我可以像这样在 find 中使用 if 语句:

if [ $(find bla-bla | wc -l) -gt 30 ]; then
...
fi

但我正在徘徊如何删除这些文件。

清楚了吗?例如,我们在 "build" 文件夹中有 100 个版本,并且所有版本都超过 30 天。所以我想保留 30 个新构建并删除另外 70 个。

这将列出除 30 个最新目录之外的所有目录。

find -type d -name builds -exec ls -d -l --time-style="+%s" {} \;|sed "s#[^ ]\+ \w\+ \w\+ \w\+ \w\+ ##"|sort -r |sed "s#[^ ]\+ ##"|tail -n +31

确定要删除它们后,您可以使用 | xargs rm -rf

它是这样读的:

  • 找到所有构建目录
  • 用纪元的时间列出它们
  • drop (sed - away) 权限,用户,组atc,只留下时间和名字
  • 从最新开始按时间排序
  • 放弃那些时间
  • tail 将显示从 31. 条目开始的所有内容(因此跳过最新的 30 个)

相当hacky,但对于奇怪的文件名应该非常健壮

find -type d -name "builds" -mtime +30 -printf "%T@ %p[=10=]" |\
awk -vRS="[=10=]" -vORS="[=10=]" '{match([=10=],/([^ ]* )(.*)/,a);b[a[2]]=a[1];c[a[1]]=a[2]}END{x=asort(b);for(i=x-30;i>0;i--)print c[b[i]]}' |\
xargs -0 -I{} rm -r {}

我用 echo 测试过,它似乎可以工作,但我会确保它在使用 rm -r 之前显示正确的文件。

所以它所做的是传递以空字符结尾的字符串,以便保留文件名。

主要限制是,如果在同一秒内创建了两个文件,那么它会遗漏一个,因为它使用了关联数组。

这是一个相对安全的答案来列出目录,如果你的stat足够接近我的(cygwin/bash):

now=$(date +%s)
find $jenkins_jobs -type d -name builds -exec find {} -type d |
  while read f; do stat -c'%Y %n' "$f"; done |
  sort -nr |
  tail -n +31 |
  awk $now'->2592000'|
  sed 's/^[0-9]* //'

这是使用 date%sstat%Y 提供的纪元时间(自 1970 年以来的秒数)。 sorttail 正在删除最新的 30 个,awk 正在删除任何 30 天或更新的日期。 (2592000 是 30 天的秒数。)最后的 sed 只是删除了 stat 添加的内容,只留下目录名。