【问题标题】:Remove all files older than X days, but keep at least the Y youngest [duplicate]删除所有早于 X 天的文件,但至少保留 Y 最年轻的文件 [重复]
【发布时间】:2013-12-19 23:10:19
【问题描述】:

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

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

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

但是,如果出于某种原因,DB 转储作业有一段时间未能完成,所有转储最终都会被丢弃。因此,作为保障,我希望至少保留最年轻的 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

(这个 sn-p 可能有一些小错误 - 忽略它们。这是为了说明我自己能想出什么,以及为什么我不喜欢它)

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

【问题讨论】:

  • 我投票结束这个问题,因为下面的答案似乎都不能正确回答这个问题。呈现的副本有一个完美的有效答案。
  • mtime 在几天内工作时更容易使用。 -mmin n 文件的数据最后一次修改是在 n 分钟前。 -mtime n 文件的数据最后一次修改是在 n*24 小时前

标签: bash shell command-line find


【解决方案1】:

使用find 获取所有足以删除的文件,使用tail 过滤掉最年轻的$KEEP,然后将其余的传递给xargs

find ${DB_DUMP_DIR} -type f -printf '%T@ %p\n' -mmin +$RETENTION |
  sort -nr | tail -n +$KEEP |
  xargs -r echo

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

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

【讨论】:

  • 即使不需要,这(就像大卫的回答一样)总是会留下 7 个早于 $RETENTION 的文件。注意 "tail -n +$KEEP" 是一次性的,它应该是 "tail -n +$((KEEP+1))" 我喜欢 "xargs",我会玩的。尽管仍然必须剥离时代。
  • 如果 KEEP
  • 与上面的 cmets 相同,但可能更清楚地说明:您不能在排序和尾部之前过滤修改时间,那么您将只保留那些早于 $RETENTION 的 $KEEP,这不是是需要的,这里和一般情况下都不是。
【解决方案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 扩展。风险很小,但修复很容易。 (理想情况下,这些变量也应转换为小写。)
【解决方案3】:

这些答案都不适合我,所以我调整了 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 最旧的,而不是最年轻的。

【讨论】:

【解决方案4】:

您可以使用-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天年龄最小的文件。

从那里,您可以将其送入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天超过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}'

将打印出所有文件除了七个最年轻的文件 em>之外。也许我们可以随之而来?

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,因为是一个off:“-mtime +21”意味着“至少22天”。这总是让我感到困惑,所以我使用-mmin。可能仍然是一次性的,但我很快就会离开我的一分钟。 span>
  • 如你所说,即使不需要,也会始终留下我的“超过21天的最年轻的文件”。最后一个命令只会让我留下7个最小的整体。有趣,但没有回答我的问题。 span>
  • 使用find ... -printf "%T@ %p"允许您删除while-stat循环 span>
  • 我想知道为什么你在-mmin。感谢您的解释。我希望能够删除超过21天的所有文件,但保持至少7.可能是更好的方法(如果它们都在一个目录中,则将在ls -t | stat .... | awk和awk程序中,如果日期> = 21天,删除它。也许我会修改我的答案来使用它。这将消除最小的七个,但是在保持其余的同时删除任何21天。 span>
  • 好的,我添加了第二种方法。我使用Mac,所以我没有所有GNU实用程序。例如,我的datestat命令是不同的。 span>
【解决方案5】:

我最终使用的是:

  • 始终保留最后 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) ]]

【讨论】:

    【解决方案6】:

    你可以自己做循环:

    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}
    • 是的。但是,使用 bash 的 [[ ]] 意味着您需要更少的引用:[[ ${date%.*} -lt $t21 ]]。点也不是一个特殊的全局字符,所以你不必转义它:${date%\.[0-9]*} 表示“删除一个点,后跟一个数字,后跟零个或多个任何字符”。如果你想删除严格的数字,你需要shopt -s extglob 然后${date%.*([0-9])}——见gnu.org/software/bash/manual/bashref.html#Pattern-Matching
    • 但这对于保留所需数量的文件无济于事,无论它们有多旧。
    【解决方案7】:

    这是一个 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 扩展名。您可以在 cmets 中看到其他一些假设,主要是以安全的名义。

    感谢 ireardonhis answer(他们完全回答问题)的启发!

    快乐的安全备份管理:)

    【讨论】:

    • 如你所见,比起手动计算分钟,我更喜欢mtime 的怪癖。使用mmin,您应该能够删除古怪的temp 变量,但在接近调用函数的时间创建备份时,结果会出现轻微的不确定性:没有什么灾难性的。
    • 易感性怎么样? :)
    • 嗯?你的意思是易读性还是可用性?好吧,您可以将“temp”变量重命名为更有意义的名称(inverted_mtime)并添加更多配置(例如每个文件类型)。否则,如果您知道 bash 并且熟悉 findsorthead, uniq, wc, cut, and xargs`(非常标准的 unix 工具),这应该是完全易读的你。如果你不是,它们只是几个手册页或谷歌搜索。
    • 硬编码find 的路径实在是太古怪了。只需确保您的 PATH 正确无误。
    • 是的,完全正确。但通常你会相信用户 ho 对系统实用程序有一个健全的路径;或者是想要覆盖系统版本的充分理由,您将通过覆盖他们的偏好来破坏它。
    【解决方案8】:

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

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

      # 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,这就是为什么:

    假设我的备份脚本坏了,一个月没有备份。我真的不在乎拥有超过 30 天的 20 个最新文件。至少拥有一个是我想要的。 但是,能够轻松识别出问题是非常重要的(显然我的监控系统确实是盲目的,但这是另一点)。让我的备份文件夹中的文件比平时少 10 倍,这可能会敲响警钟……

    【讨论】:

    • 看起来像我的解决方案,除了你创建了三个临时文件并做了一些额外的 grepping :p
    • 您确实希望避免使用临时文件。如果无法避免,您确实必须避免使用静态临时文件名。该解决方案称为mktemp
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 2020-03-11
    • 1970-01-01
    相关资源
    最近更新 更多