删除所有超过X天的文件,但至少保留Y最年轻的文件

时间:2013-12-03 18:28:12

标签: bash shell command-line find

我有一个脚本可以删除比备份目录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代替。仍然是一次性的,但只有一分钟。

8 个答案:

答案 0 :(得分:3)

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

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

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

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

答案 1 :(得分: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天的所有文件,但不会列出超过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}'

将打印除<7个最年轻的文件之外的所有文件。也许我们可以坚持下去?

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天以上的文件,但始终保持七个最年轻。

答案 2 :(得分:1)

我正在打开第二个答案因为我只有一个不同的解决方案 - 一个使用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

答案 3 :(得分: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) ]]

答案 4 :(得分: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

答案 5 :(得分:0)

这些答案对我来说都不起作用,所以我改编了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 最旧的,而不是最年轻的。

答案 6 :(得分: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,这就是为什么:

让我们想象一下我的备份脚本已损坏,而我一个月都没有备份。我真的不在乎拥有30天以上的20个最新文件。我想要至少拥有一个。 但是,能够轻松识别出问题非常重要(显然我的监视系统确实是盲目的,但这是另一点)。而且,我的备份文件夹中的文件比平时少10倍,这也许会引起注意……

答案 7 :(得分:-1)

这里是一个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(不能完全回答问题)来获得灵感!

快乐的安全备份管理:)