首页 文章

删除bash中除最新的X文件以外的所有文件

提问于
浏览
118

有一个简单的方法,在一个非常标准的UNIX环境中使用bash运行命令来删除目录中除最新的X文件之外的所有文件吗?

为了给出一个具体的例子,想象一下一些cron作业每小时写一个文件(比如一个日志文件或一个tar-up up备份)到一个目录 . 我想要一种方法来运行另一个cron作业,这将删除该目录中最旧的文件,直到少于5个 .

而且要明确的是,只有一个文件存在,它永远不应该被删除 .

17 回答

  • 56
    find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f
    

    需要GNU查找-printf,GNU排序为-z,GNU awk为“\ 0”,GNU xargs为-0,但处理带有嵌入换行符或空格的文件 .

  • 1

    删除除10个最新(最近的)文件之外的所有文件

    ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm
    

    如果少于10个文件没有删除文件,您将拥有:错误头:非法行数 - 0

    To count files with bash

  • 13

    在Debian上运行(假设它在我得到的其他发行版上是相同的:rm:无法删除目录`..'

    这很烦人..

    无论如何,我调整了上面的内容,并在命令中添加了grep . 在我的情况下,我在目录中有6个备份文件,例如file1.tar file2.tar file3.tar等我想删除最旧的文件(在我的情况下删除第一个文件)

    我运行的删除最旧文件的脚本是:

    ls -C1 -t | grep文件| awk'NR> 5'| xargs rm

    这(如上所述)删除了我的第一个文件,例如file1.tar这也留下了file2 file3 file4 file5和file6

  • 0

    更简单的thelsdj答案:

    ls -tr | head -n -5 | xargs --no-run-if-empty rm
    

    ls -tr显示所有文件,最早的文件(-t最新的第一个,-r反向) .

    head -n -5显示除最后5行之外的所有行(即5个最新文件) .

    xargs rm为每个选定的文件调用rm .

  • 79

    我意识到这是一个老线程,但也许有人会从中受益 . 此命令将在当前目录中查找文件:

    for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done
    

    这比以前的一些答案更加健壮,因为它允许将搜索域限制为匹配表达式的文件 . 首先,找到符合您想要的任何条件的文件 . 打印带有时间戳的文件 .

    find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'
    

    接下来,按时间戳排序:

    sort -r -z -n
    

    然后,从列表中删除最近的4个文件:

    tail -n+5
    

    grab 第二列(文件名,而不是时间戳):

    awk '{ print $2; }'
    

    然后将整个内容包装成for语句:

    for F in $(); do rm $F; done
    

    这可能是一个更详细的命令,但我有更好的运气能够针对条件文件并执行更复杂的命令 .

  • 8
    leaveCount=5
    fileCount=$(ls -1 *.log | wc -l)
    tailCount=$((fileCount - leaveCount))
    
    # avoid negative tail argument
    [[ $tailCount < 0 ]] && tailCount=0
    
    ls -t *.log | tail -$tailCount | xargs rm -f
    
  • 16

    现有答案存在的问题:

    • 无法处理带有嵌入空格或换行符的文件名 .

    • 对于直接在不带引号的命令替换( rm...`` )上调用 rm 的解决方案,会增加意外通配的风险 .

    • 无法区分文件和目录(即,如果目录恰好是最近修改的5个文件系统项目之一,则实际上保留少于5个文件,并且将 rm 应用于目录将失败) .

    wnoise's answer解决了这些问题,但解决方案是GNU特定的(并且非常复杂) .

    这是一个实用的, POSIX-compliant solution 只有 one caveat :它无法处理带有嵌入换行符的文件名 - 但我不认为这对大多数人来说是一个现实问题 .

    为了记录,这里解释为什么解析ls输出通常不是一个好主意:http://mywiki.wooledge.org/ParsingLs

    ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
    

    以上是 inefficient ,因为 xargs 必须为每个文件名调用 rm 一次 .
    您的平台的 xargs 可能允许您解决此问题:

    如果您有 GNU xargs ,请使用 -d '\n' ,这会使 xargs 将每个输入行视为一个单独的参数,但会传递尽可能多的参数,以适应命令行:

    ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
    

    -r( - no-run-if-empty)确保在没有输入的情况下不调用rm .

    如果你有 BSD xargs (包括在 OS X 上),你可以使用 -0 来处理 NUL 分离的输入,首先将换行符转换为 NUL0x0 )字符,这也会一次传递(通常)所有文件名(也适用于GNU xargs ) ):

    ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --
    

    Explanation:

    • ls -tp 打印文件系统项目的名称,按照最近修改的顺序排序,按降序排列(最近修改的项目)( -t ),目录打印有尾随 / 以将其标记为( -p ) .

    • grep -v '/$' 然后通过省略( -v )具有尾随 //$ )的行来清除结果列表中的目录 .

    • 警告:由于指向目录的符号链接在技术上本身不是目录,因此不会排除此类符号链接 .

    • tail -n +6 跳过列表中的前5个条目,实际上返回除了最近修改的5个文件之外的所有文件(如果有的话) .
      请注意,为了排除 N 文件,必须将 N+1 传递给 tail -n + .

    • xargs -I {} rm -- {} (及其变体)然后在 rm 上调用所有这些文件;如果根本没有匹配, xargs 将不会做任何事情 .

    • xargs -I {} rm -- {} 定义占位符 {} ,它代表每个输入行的整体,因此 rm 然后为每个输入行调用一次,但是处理了嵌入空格的文件名正确 .
      在所有情况下

    • -- 确保碰巧以 - 开头的任何文件名都不会被 rm 误认为是选项 .


    关于原始问题 variationin case the matching files need to be processed individually or collected in a shell array

    # One by one, in a shell loop (POSIX-compliant):
    ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done
    
    # One by one, but using a Bash process substitution (<(...), 
    # so that the variables inside the `while` loop remain in scope:
    while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)
    
    # Collecting the matches in a Bash *array*:
    IFS=$'\n' read -d '' -ra files  < <(ls -tp | grep -v '/$' | tail -n +6)
    printf '%s\n' "${files[@]}" # print array elements
    
  • 85

    当前目录中有目录时,所有这些答案都会失败 . 这是有效的:

    find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm
    

    这个:

    • 在当前目录中有目录时有效

    • 尝试删除每个文件,即使无法删除上一个文件(由于权限等)

    当当前目录中的文件数量过多而 xargs 通常会让你搞砸时

    • 安全失败( -x

    • 不适合文件名中的空格(也许你使用的是错误的操作系统?)

  • 4

    在Sed-Onliners中找到了有趣的cmd - 删除了最后3行 - 它完美的另一种方式来剥皮猫(好吧没有)但想法:

    #!/bin/bash
     # sed cmd chng #2 to value file wish to retain
    
     cd /opt/depot 
    
     ls -1 MyMintFiles*.zip > BigList
     sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList
    
     for i in `cat DeList` 
     do 
     echo "Deleted $i" 
     rm -f $i  
     #echo "File(s) gonzo " 
     #read junk 
     done 
     exit 0
    
  • 84
    (ls -t|head -n 5;ls)|sort|uniq -u|xargs rm
    

    此版本支持带空格的名称:

    (ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm
    
  • -5

    删除目录中除最新文件的5个(或任何数量)之外的所有文件 .

    rm `ls -t | awk 'NR>5'`
    
  • 0

    忽略换行符会忽略安全性和良好的编码 . wnoise有唯一的好答案 . 这是他的一个变体,它将文件名放在数组$ x中

    while IFS= read -rd ''; do 
        x+=("${REPLY#* }"); 
    done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )
    
  • 12

    我需要为busybox(路由器)提供一个优雅的解决方案,所有xargs或阵列解决方案对我来说都是无用的 - 那里没有这样的命令 . find和mtime不是正确的答案,因为我们正在谈论10个项目,而不一定是10天 . Espo的答案是最短,最干净,也可能是最不可逆的答案 .

    空格出错以及没有要删除的文件都是以标准方式解决的:

    rm "$(ls -td *.tar | awk 'NR>7')" 2>&-
    

    更有教育意义的版本:如果我们使用awk的话,我们可以做到这一切 . 通常,我使用此方法将变量从awk传递(返回)到sh . 当我们阅读所有无法完成的时间时,我不同意:这是方法 .

    .tar文件的示例,文件名中的空格没有问题 . 要测试,请将“rm”替换为“ls” .

    eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')
    

    说明:

    ls -td *.tar 列出按时间排序的所有.tar文件 . 要应用于当前文件夹中的所有文件,请删除"d *.tar"部分

    awk 'NR>7... 跳过前7行

    print "rm \"" $0 "\"" 构造一条线:rm "file name"

    eval 执行它

    由于我们使用 rm ,我不会在脚本中使用上面的命令!更明智的用法是:

    (cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))
    

    在使用 ls -t 命令的情况下,对这样的愚蠢示例不会造成任何伤害: touch 'foo " bar'touch 'hello * world' . 并非我们在现实生活中创建具有此类名称的文件!

    边注 . 如果我们想以这种方式将变量传递给sh,我们只需修改print(简单形式,不容许空间):

    print "VarName="$1
    

    将变量 VarName 设置为 $1 的值 . 可以一次创建多个变量 . 这 VarName 成为普通的sh变量,之后通常可以在脚本或shell中使用 . 所以,用awk创建变量并将它们返回给shell:

    eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\""  }'); echo "$VarName"
    
  • 2

    如果文件名没有空格,这将起作用:

    ls -C1 -t| awk 'NR>5'|xargs rm
    

    如果文件名确实有空格,那就像

    ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh
    

    基本逻辑:

    • 按时间顺序获取文件列表,一列

    • 得到除前5之外的所有(本例中n = 5)

    • 第一版:发送给rm

    • 第二版:生成一个可以正确删除它们的脚本

  • 2

    我把它变成了一个bash shell脚本 . 用法: keep NUM DIR 其中NUM是要保留的文件数,DIR是要擦除的目录 .

    #!/bin/bash
    # Keep last N files by date.
    # Usage: keep NUMBER DIRECTORY
    echo ""
    if [ $# -lt 2 ]; then
        echo "Usage: $0 NUMFILES DIR"
        echo "Keep last N newest files."
        exit 1
    fi
    if [ ! -e $2 ]; then
        echo "ERROR: directory '$1' does not exist"
        exit 1
    fi
    if [ ! -d $2 ]; then
        echo "ERROR: '$1' is not a directory"
        exit 1
    fi
    pushd $2 > /dev/null
    ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {}
    popd > /dev/null
    echo "Done. Kept $1 most recent files in $2."
    ls $2|wc -l
    
  • 1
    ls -tQ | tail -n+4 | xargs rm
    

    按修改时间列出文件名,引用每个文件名 . 排除前3(最近3) . 删除剩余的 .

    在mklement0的有用评论之后编辑(谢谢!):更正了-n 3参数,并注意如果文件名包含换行符和/或目录包含子目录,这将无法按预期工作 .

  • 1

    随着zsh

    假设您不关心当前目录,并且您将不会有超过999个文件(如果需要,请选择更大的数字,或创建while循环) .

    [ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])
    

    *(.om[6,999]) 中, . 表示文件, o 表示排序顺序, m 表示修改日期(对于访问时间为 a 或对于inode更改为 c ), [6,999] 选择一个文件范围,因此不是5第一 .

相关问题