删除所有早于X天的文件,但至少保留Y个最新的。

16

我有一个脚本,用于从备份目录中删除早于X=21天的数据库转储:

DB_DUMP_DIR=/var/backups/dbs
RETENTION=$((21*24*60))  # 3 weeks

find ${DB_DUMP_DIR} -type f -mmin +${RETENTION} -delete

如果由于某种原因数据库转储作业无法完成一段时间,所有的转储最终都将被丢弃。因此,为了保障起见,我希望至少保留最年轻的Y=7个转储,即使它们中的某些数据比21天还要旧。

我正在寻找一种比这更优雅的解决方案:

DB_DUMP_DIR=/var/backups/dbs
RETENTION=$((21*24*60))  # 3 weeks
KEEP=7

find ${DB_DUMP_DIR} -type f -printf '%T@ %p\n' | \  # list all dumps with epoch
sort -n | \                                         # sort by epoch, oldest 1st
head --lines=-${KEEP} |\                            # Remove youngest/bottom 7 dumps
while read date filename ; do                       # loop through the rest
    find $filename -mmin +${RETENTION} -delete      # delete if older than 21 days
done

(这段代码可能存在一些小错误 - 请忽略它们。这只是为了说明我自己能想出什么,以及我为什么不喜欢它)

编辑:查找选项“-mtime”是单次的:“-mtime +21”实际上意味着“至少22天以上”。这总是让我感到困惑,所以我改用“-mmin”。仍然是单次的,但只有一分钟。


1
我投票将此问题关闭为较新的问题的重复,因为下面没有任何答案似乎能够正确回答这个问题。提出的重复问题有一个完美有效的答案。 - kvantour
当按天计算时,使用mtime更容易。-mmin n 文件数据上次修改是在n分钟前。 -mtime n 文件数据上次修改是在n * 24小时前。 - gaoithe
8个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
4
使用 find 命令获取所有需要删除的旧文件,使用 tail 过滤掉最新的 $KEEP 文件,然后将剩下的文件传递给 xargs 命令。
find ${DB_DUMP_DIR} -type f -printf '%T@ %p\n' -mmin +$RETENTION |
  sort -nr | tail -n +$KEEP |
  xargs -r echo

如果报告的文件列表是您想要删除的列表,请使用rm替换echo

(我假设转储文件的名称中没有换行符。)


2
这个(就像David的回答一样)即使不需要,也总是会留下7个比$RETENTION旧的文件。请注意,“tail -n +$KEEP”是一次性的,应该是“tail -n +$((KEEP+1))”。我喜欢“xargs”,我会试试。虽然仍然需要去掉时间戳。 - Nils Toedtmann
如果KEEP < RETENTION(通常是所需的,并且符合OP的条件),则此方法无效。 - Orabîg
与上述评论相同,但可能需要更清晰地说明:在进行排序和截取之前,您无法根据修改时间进行过滤,然后您将仅保留早于$RETENTION的$KEEP个文件,这不是所需的,无论是在此处还是一般情况下都是如此。 - Marcus Philip

2

对我来说,这些答案都不太适用,所以我改编了chepner的答案并得出了这个结果,它只是保留最后的$KEEP备份。

find ${DB_DUMP_DIR} -printf '%T@ %p\n' | # print entries with creation time
  sort -n |                              # sort in date-ascending order
  head -n -$KEEP |                       # remove the $KEEP most recent entries
  awk '{ print $2 }' |                   # select the file paths
  xargs -r rm                            # remove the file paths

我认为chepner的代码保留了$KEEP 最老的内容,而不是最年轻的。


谢谢!我用它飞起来了:https://dev59.com/GWIj5IYBdhLWcg3wWT6c#52230709 ;) - DylanYoung
“head | awk” 可能很容易地重构为一个 Awk 脚本。 - tripleee
但是这个答案忽略了$RETENTION。 - Marcus Philip
为了保留,您也可以在查找命令中添加“-mtime +21”,以仅考虑早于21天的文件。 - Edmond Burnett

2

我开了一个新的回答,因为我有一个不同的解决方案 - 使用 awk:只需将时间添加到 21 天(以秒为单位)的期限中,减去当前时间并删除负数!(在对列表进行排序并删除最新的7个之后):

DB_DUMP_DIR=/var/backups/dbs
RETENTION=21*24*60*60  # 3 weeks
CURR_TIME=`date +%s`

find ${DB_DUMP_DIR} -type f -printf '%T@ %p\n' | \
  awk '{ print int($1) -'${CURR_TIME}' + '${RETENTION}' ":" $2}' | \
  sort -n | head -n -7 | grep '^-' | cut -d ':' -f 2- | xargs rm -rf

不引用 ${RETENTION} 意味着它可能会被 shell 扩展。风险很小,但修复很容易。(理想情况下,这些变量也应转换为小写。) - tripleee

1
你可以使用-mtime代替-mmin,这意味着你不需要计算一天中的分钟数:
find $DB_DUMP_DIR -type f -mtime +21

你可以使用stat命令将文件按顺序排序,而不是删除它们:

find $DB_DUMP_DIR -type f -mtime +21 | while read file
do
    stat -f "%-10m %40N" $file
done | sort | awk 'NR > 7 {print $2}'
这将列出所有早于21天的文件,但不包括7个年龄大于21天的最年轻文件。 然后,您可以将其输入xargs中进行删除操作。
find $DB_DUMP_DIR -type f -mtime +21 | while read file
do
    stat -f "%-10m %40N" $file
done | sort | awk 'NR > 7 {print $2]' | xargs rm

当然,这一切都是基于您的文件名中没有空格。如果有空格,您需要采取略微不同的方式。

这也将保留超过21天的七个最年轻的文件。您可能有比那还要年轻的文件,并且并不想真正保留它们。但是,您可以简单地再次运行相同的序列(除了删除-mtime参数):

find $DB_DUMP_DIR -type f |  while read file
do
    stat -f "%-10m %40N" $file
done | sort | awk 'NR > 7 {print $2} | xargs rm
你需要查看 stat 命令,了解格式选项。这因系统而异。我使用的是适用于 OS X 的选项。Linux 则不同。

我们来换一种稍微不同的方法。我没有彻底测试过这个,但是:

如果所有的文件都在同一个目录下,并且没有文件名中带有空格:

ls -t | awk 'NR > 7 {print $0}'

将打印出所有文件,除了最年轻的七个文件。也许我们可以采用这种方式?

current_seconds=$(date +%S)   # Seconds since the epoch
((days = 60 * 60 * 24 * 21))  # Number of seconds in 21 days
((oldest_allowed = $current_seconds - $days)) # Oldest allowed file
ls -t | awk 'NR > 7 {print $0}' | stat -f "%Dm %N" $file | while date file
do
    [ $date < $oldest_allowed ] || rm $file
done
ls ... | awk将删除最近的七个文件。然后,我们可以使用stat获取文件名和日期。由于日期是纪元后的秒数,所以我们必须计算当前时间之前21天在纪元前的秒数。

此后就很简单了。我们查看文件的日期。如果它比纪元前21天更早(即时间戳较低),我们可以删除它。

正如我所说,我还没有彻底测试过这个程序,但它会删除所有超过21天的文件,并且仅删除超过21天的文件,但始终保留最近的七个文件。


我不使用-mtime,因为它只有一次性效果:"-mtime +21" 实际上意思是 "至少22天前"。这总是让我感到困惑,所以我改用 -mmin。可能仍然有一次性效果,但我可以接受少几分钟的误差。 - Nils Toedtmann
1
正如你所说,即使不需要,这将始终留下“21天以上的七个最年轻的文件”。而最后一个命令只会留下我最年轻的7个文件。这很有趣,但并没有回答我的问题。 - Nils Toedtmann
使用 find ... -printf "%T@ %p" 可以让你避免使用 while-stat 循环。 - glenn jackman
我想知道你为什么使用了“-mmin”。感谢解释。我想要能够删除所有超过21天的文件,但至少保留7个。也许更好的方法(如果它们都在一个目录中)是ls -t | stat .... | awk,在awk程序中,如果日期> = 21天,则删除它。也许我会修改我的答案来使用它。这将消除最年轻的七个,然后删除任何超过21天的文件,同时保留其余的文件。 - David W.
好的,我添加了第二种方法。我使用的是Mac电脑,所以我没有所有的GNU工具。例如,我的“date”和“stat”命令略有不同。 - David W.
@DavidW。你可以从Homebrew获取GNU工具。强烈推荐。 - DylanYoung

1
我最终使用的方案是:
  • 始终保留最后N个项目
  • 对于其余项目,如果文件早于X天,则删除它
for f in $(ls -1t | tail -n +31); do
   if [[ $(find "$f" -mtime +30 -print) ]]; then
      echo "REMOVING old backup: $f"
      rm $f
   fi
done

说明:

按时间排序的ls,跳过前30个项目:$(ls -1t | tail -n +31)

测试find是否可以找到文件的创建时间早于30天:if [[ $(find "$f" -mtime +30 -print) ]]


0

这里有一个BASH函数,应该可以解决问题。我很难避免两次调用find,但除此之外,它是相对成功的:

#  A "safe" function for removing backups older than REMOVE_AGE + 1 day(s), always keeping at least the ALWAYS_KEEP youngest
remove_old_backups() {
    local file_prefix="${backup_file_prefix:-$1}"
    local temp=$(( REMOVE_AGE+1 ))  # for inverting the mtime argument: it's quirky ;)
    # We consider backups made on the same day to be one (commonly these are temporary backups in manual intervention scenarios)
    local keeping_n=`/usr/bin/find . -maxdepth 1 \( -name "$file_prefix*.tgz" -or -name "$file_prefix*.gz" \) -type f -mtime -"$temp" -printf '%Td-%Tm-%TY\n' | sort -d | uniq | wc -l`
    local extra_keep=$(( $ALWAYS_KEEP-$keeping_n ))

    /usr/bin/find . -maxdepth 1 \( -name "$file_prefix*.tgz" -or -name "$file_prefix*.gz" \) -type f -mtime +$REMOVE_AGE -printf '%T@ %p\n' |  sort -n | head -n -$extra_keep | cut -d ' ' -f2 | xargs -r rm
}

它需要一个backup_file_prefix环境变量或者可以作为第一个参数传递,并期望环境变量ALWAYS_KEEP(要保留的最小文件数)和REMOVE_AGE(要传递给-mtime 的天数)。它期望一个gztgz扩展名。正如您在注释中看到的,还有一些其他假设,主要是出于安全考虑。

感谢ireardonhis answer(这个回答并不完全回答问题)的启示!

祝您备份管理愉快安全 :)


正如您所看到的,我更喜欢使用mtime的怪癖而不是手动计算分钟。使用mmin,您应该能够在备份创建时间接近函数调用时间时消除奇怪的temp变量,但结果可能会有轻微的不确定性,这并不会造成灾难性后果。 - DylanYoung
1
可读性怎么样? :) - Orabîg
1
什么?你是指易读性还是可用性?那么,你可以将“temp”变量重命名为更有意义的名称(例如“inverted_mtime”),并添加一些更多的配置(例如按文件类型)。否则,如果你了解bash并熟悉findsortheaduniqwccutxargs(这些都是相当标准的Unix工具),那么这对你来说应该是非常易读的。如果你不了解,它们只是一些手册或谷歌搜索的距离。 - DylanYoung
@tripleee 用户有时会修改他们的PATH。这确保只使用系统查找。我更喜欢有一种更标准的方法来访问程序的系统版本,但我不知道有没有这样的方法。如果您确信用户设置了正确的路径,请随意删除前缀 :) - DylanYoung
1
是的,没错。但通常情况下,您会相信用户对于系统实用程序有一个合理的路径;或者有一个很好的理由想要覆盖系统版本,而您通过覆盖他们的偏好设置将会破坏这个版本。 - tripleee
显示剩余3条评论

0
你可以自己做循环:
t21=$(date -d "21 days ago" +%s)
cd "$DB_DUMP_DIR"
for f in *; do
    if (( $(stat -c %Y "$f") <= $t21 )); then
        echo rm "$f"
    fi
done

我假设你有GNU date


谢谢你提供'date -d "21 days ago" +%s',我之前不知道这个命令。所以在我的示例脚本中,我可以修改while循环块为:[ "${date%\.[0-9]*}" -lt "${t21}" ] && echo rm ${filename} - Nils Toedtmann
是的。但是使用bash的[[ ]]意味着您需要更少的引用:[[ ${date%.*} -lt $t21 ]]。此外,点不是特殊的glob字符,因此您不必对其进行转义:${date%\.[0-9]*}表示“删除一个点,后跟一个数字,后跟零个或多个任何字符”。如果您想要严格删除数字,则需要shopt -s extglob,然后${date%.*([0-9])}--请参见http://www.gnu.org/software/bash/manual/bashref.html#Pattern-Matching - glenn jackman
但是这并不能保持所需的文件数量,无论它们有多旧。 - tripleee

0

从其他解决方案中给出的解决方案中,我进行了实验,并发现了许多不需要的错误或情况。

这是我最终想出的解决方案:

  # Sample variable values
  BACKUP_PATH='/data/backup'
  DUMP_PATTERN='dump_*.tar.gz'
  NB_RETENTION_DAYS=10
  NB_KEEP=2                    # keep at least the 2 most recent files in all cases

  find ${BACKUP_PATH} -name ${DUMP_PATTERN} \
    -mtime +${NB_RETENTION_DAYS} > /tmp/obsolete_files

  find ${BACKUP_PATH} -name ${DUMP_PATTERN} \
    -printf '%T@ %p\n' | \
    sort -n            | \
    tail -n ${NB_KEEP} | \
    awk '{ print $2 }'   > /tmp/files_to_keep

  grep -F -f /tmp/files_to_keep -v /tmp/obsolete_files > /tmp/files_to_delete

  cat /tmp/files_to_delete | xargs -r rm

这些想法是:

  • 大多数情况下,我只想保留未超过NB_RETENTION_DAYS的文件。
  • 然而,有时候会出现问题,如果没有最近的文件了(备份脚本出了问题),为了安全起见,我不想删除更近的NB_KEEP文件(NB_KEEP至少应该为1)。

在我的情况下,我每天备份2次,并将NB_RETENTION_DAYS设置为10(因此,在正常情况下,我通常有20个文件)。 有人可能认为我应该将NB_KEEP设置为20,但实际上,我选择了NB_KEEP=2,原因如下:

假设我的备份脚本出了问题,一个月内我没有备份。我真的不在乎我最近的20个文件是否超过30天。我只想要至少一个。 然而,能够轻松地识别出问题非常重要(显然我的监控系统真的很瞎,但这是另一个问题)。而且,备份文件夹比平时少10倍的文件可能会引起注意...


看起来和我的解决方案差不多,只是你创建了三个临时文件并进行了一些额外的 grep 操作 :p - DylanYoung
你真的想避免使用临时文件。如果不能避免,你真的必须避免使用静态临时文件名。解决方案被称为 mktemp - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,