删除目录中最旧的文件,当有超过 7 个文件时?

beg*_*27_ 6 mysql 16.04

我必须创建一个 MySQL 数据库的备份脚本 (bash)。当我执行脚本时,将在“/home/user/Backup”中创建一个 sql 文件。问题是,如果“.../Backup”中有超过 7 个文件,我还必须制作一个脚本来删除最旧的文件。有人知道怎么做吗?我尝试了所有方法,但每次都无法计算目录中的文件并检测到最旧的文件...

Ser*_*nyy 3

介绍

让我们回顾一下这个问题:任务是检查特定目录中的文件数量是否超过一定数量,并删除其中最旧的文件。乍一看,我们需要遍历目录树一次统计文件,然后再次遍历找到所有文件的最后修改时间,对它们进行排序,并提取最旧的进行删除。但考虑到在这种特殊情况下,OP提到当且仅当文件数量超过7时才删除文件,这表明我们可以简单地获取所有文件及其时间戳的列表一次,并将它们存储到一个变量中。

这种方法的问题是与文件名相关的危险。正如评论中所提到的,永远不建议解析ls命令,因为输出可能包含特殊字符并破坏脚本。但是,正如你们中的一些人可能知道的那样,在类 Unix 系统(以及 Ubuntu)中,每个文件都有与之关联的索引节点号。因此,创建一个带有时间戳的条目列表(以秒为单位,以便于数字排序)加上由换行符分隔的索引节点号将保证我们安全地解析文件名。删除最旧的文件名也可以通过这种方式完成。

下面提供的脚本与上面描述的完全一样。

脚本

重要提示:请阅读评论,尤其是delete_oldest功能方面的评论。

#!/bin/bash
# Uncomment line below for debugging
#set -xv
delete_oldest(){
     # reads a line from stdin, extracts file inode number
     # and deletes file to which inode belongs
     # !!! VERY IMPORTANT !!!
     # The actual command to delete file is commented out.
     # Once you verify correct execution, feel free to remove
     # leading # to uncomment it
     read timestamp file_inode
     find "$directory" -type f -inum "$file_inode"  -printf "Deleted %f\n" 
     # find "$directory" -type f -inum "$file_inode"  -printf "Deleted %f\n"  -delete
}

get_files(){
    # Wrapper function around get files. Ensures we're working
    # with files and only on one specific level of directory tree
    find "$directory" -maxdepth 1 -type f  -printf "%Ts\t%i\n" 
}

filecount_above_limit(){
    # This function counts number of files obtained
    # by get_files function. Returns true if file
    # count is greater than what user specified as max
    # value 
    num_files=$(wc -l <<< "$file_inodes"  )
    if [ $num_files -gt "$max_files" ];
    then
        return 0
    else
        return 1
    fi
}

exit_error(){
    # Print error string and quit
    printf ">>> Error: %s\n"  "$1" > /dev/stderr 
    exit 1
}

main(){
    # Entry point of the program. 
    local directory=$2
    local max_files=$1

    # If directory is not given
    if [ "x$directory" == "x"  ]; then
        directory="."
    fi

    # check arguments for errors
    [ $# -lt 1  ] && exit_error "Must at least have max number of files"
    printf "%d" $max_files &>/dev/null || exit_error "Argument 1 not numeric"
    readlink -e "$directory" || exit_error "Argument 2, path doesn't exist"

    # This is where actual work is being done
    # We traverse directory once, store files into variable.
    # If number of lines (representing file count) in that variable
    # is above max value, we sort numerically the inodes and pass them
    # to delete_oldest, which removes topmost entry from the sorted list
    # of lines.
    local file_inodes=$(get_files)
    if filecount_above_limit 
    then
        printf  "@@@ File count in %s is above %d." "$directory" $max_files
        printf "Will delete oldest\n"
        sort -k1 -n <<< "$file_inodes" | delete_oldest
    else
        printf "@@@ File count in %s is below %d."  "$directory" $max_files
        printf "Exiting normally"
    fi
}

main "$@"
Run Code Online (Sandbox Code Playgroud)

使用示例

$ ./delete_oldest.sh 7 ~/bin/testdir                                                                                     
/home/xieerqi/bin/testdir
@@@ File count in /home/xieerqi/bin/testdir is below 7.Exiting normally
$ ./delete_oldest.sh 7 ~/bin                                                                                             
/home/xieerqi/bin
@@@ File count in /home/xieerqi/bin is above 7.Will delete oldest
Deleted typescript
Run Code Online (Sandbox Code Playgroud)

补充讨论

这可能很可怕。。.而且冗长。。.而且看起来它做得太多了。也可能是这样。事实上,所有内容都可以推到一行命令行上(聊天中发布的处理文件名的 muru 建议的经过大量修改的版本。echo用于而不是rm用于演示目的):

find /home/xieerqi/bin/testdir/ -maxdepth 1 -type f -printf "%T@ %p\0" | sort -nz | { f=$(awk  'BEGIN{RS=" "}NR==2{print;next}'  ); echo "$f" ; }
Run Code Online (Sandbox Code Playgroud)

然而,我对此有几点不喜欢的地方:

  • 它无条件删除最旧的文件,而不检查目录中的文件数量
  • 它直接处理文件名(这需要我使用尴尬的awk命令,这可能会破坏带有空格的文件名)
  • 太多的管道(太多的管道)

因此,虽然我的脚本对于简单的任务来说看起来非常巨大,但它会进行更多的检查,旨在解决复杂文件名的问题。用 Perl 或 Python 实现可能会更短、更惯用(我绝对可以做到,我只是碰巧选择了bash这个问题)。


sud*_*dus 1

我认为@Serg 的回答很好,我正在向他和@muru 学习。find我做出这个答案是因为我想探索和学习如何根据“操作”的输出创建 shellscript 文件,-print以根据文件的创建/修改时间对文件进行排序。请提出改进​​和错误修复建议(如有必要)。

您会注意到,编程风格非常不同。我们可以在 Linux 中以多种方式做事:-)

我制作了一个 bash shell 脚本来满足 OP @beginner27_ 的要求,但为了其他但类似的目的修改它并不太困难。

下面的屏幕截图显示了它是如何测试的:创建了 11 个文件,并且运行了脚本(位于 ~/bin 中并具有执行权限)。我已从该行中删除了 # 字符

# bash "$cmd"
Run Code Online (Sandbox Code Playgroud)

做到这一点

bash "$cmd"
Run Code Online (Sandbox Code Playgroud)

脚本第一次发现并打印 11 个文件,其中 7 个最新文件具有蓝色背景,4 个最旧文件具有红色背景。四个最旧的文件将被删除。该脚本第二次运行(仅用于演示)。它发现并打印剩余的七个文件,并满意地回答“没有要删除的备份文件”。

在此输入图像描述

find根据时间对文件进行排序的关键命令如下所示,

find "$bupdir" -type f -printf "%T+ %p\0"|sort -nrz > "$srtlst"
Run Code Online (Sandbox Code Playgroud)

这是脚本文件。我将其保存~/bin为名称rm_old_backups,但您可以给它任何名称,只要它不干扰可执行程序的某些已存在的名称即可。

#!/bin/bash

keep=7  # set the number of files to keep

# variables and temporary files

inversvid="\0033[7m"
resetvid="\0033[0m"
redback="\0033[1;37;41m"
greenback="\0033[1;37;42m"
blueback="\0033[1;37;44m"

bupdir="$HOME/Backup"
cmd=$(mktemp)
srtlst=$(mktemp)
rmlist=$(mktemp)

# output to the screen

echo -e "$inversvid$0:
keep $keep backup files, remove the oldest files, if more than $keep are found $resetvid"

echo "Security fix: You must edit this script and remove the # character from
a line near the end of the script '# bash \"\$cmd\"' --> 'bash \"\$cmd\"'
otherwise the script will only show what it can do. Please test that it
works correctly before you remove that # character!"

# the crucial find command, that sorts the files according to time

find "$bupdir" -type f -printf "%T+ %p\0"|sort -nrz > "$srtlst"

# more output

echo -e "${inversvid}time-stamp                     file-name                               $resetvid"
echo -en "$blueback"
sed -nz -e 1,"$keep"p "$srtlst" | tr '\0' '\n'
echo -en "$resetvid"

echo -en "$redback"
sed -z -e 1,"$keep"d "$srtlst" | tr '\0' '\n' | tee "$rmlist"
echo -en "$resetvid"

# remove oldest files if more files than specified are found

if test -s "$rmlist"
then
 echo rm '"'$(sed -z -e 1,"$keep"d -e 's/[^ ]* //' -e 's/$/" "/' "$srtlst")'"'\
 | sed 's/" ""/"/' > "$cmd"
 cat "$cmd"

# uncomment the following line to really remove files 
# bash "$cmd"

 echo "The oldest backup files are removed"
else
 echo "There is no old backup file to remove"
fi

# remove temporary files

rm $cmd $srtlst $rmlist
Run Code Online (Sandbox Code Playgroud)