当前位置:首页> 正文

关于Unix:删除bash中除最新的X文件以外的所有文件

关于Unix:删除bash中除最新的X文件以外的所有文件

Delete all but the most recent X files in bash

在带有bash的标准UNIX环境中,是否有一种简单的方法来运行命令以从目录中删除除最新的X文件以外的所有文件?

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

而且要清楚一点,只有一个文件存在,永远不要删除它。


现有答案存在的问题:

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

    • 如果解决方案直接在无引号的命令替换(rm `...`)上调用rm,则存在意外滚动的风险。
  • 无法区分文件和目录(即,如果目录恰好是最近修改的5个文件系统项之一,则您实际上将保留少于5个文件,并且将rm应用于目录将失败)。

wnoise的答案解决了这些问题,但是解决方案是特定于GNU的(而且相当复杂)。

这是一个实用的,符合POSIX的解决方案,只有一个警告:它无法处理带有嵌入式换行符的文件名-但我认为对于大多数人来说,这并不是现实问题。

作为记录,以下是为什么解析ls输出通常不是一个好主意的说明:http://mywiki.wooledge.org/ParsingLs

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

上面的方法效率低下,因为xargs必须为每个文件名调用一次rm
您平台的xargs可以让您解决此问题:

如果您具有GNU xargs,请使用-d '
'
,这会使xargs将每条输入行视为一个单独的参数,但会同时传递与命令行中相同数量的参数:

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

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

如果您具有BSD xargs(包括OS X),则可以在第一次将换行符转换为NUL(0x0)字符后使用-0处理NUL分隔的输入。 )一次所有文件名(也将与GNU xargs一起使用):

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

说明:

  • ls -tp打印文件系统项的名称,这些文件系统项的名称按降序排列(降序排列(首先是最近修改的项))(-t),并在目录上打印尾随/以将其标记为()。
  • grep -v '/$'然后通过省略(-v)行尾带有/(/$)的行从结果列表中清除目录。

    • 注意:由于指向目录的符号链接在技术上本身并不是目录,因此不会排除此类符号链接。
  • tail -n +6跳过列表中的前5个条目,实际上返回除5个最近修改的文件(如果有)以外的所有文件。
    请注意,为了排除N文件,必须将N+1传递给tail -n +
  • 然后xargs -I {} rm -- {}(及其变体)在所有这些文件上的rm上调用;如果根本没有匹配项,则xargs将不执行任何操作。

    • xargs -I {} rm -- {}定义占位符{},该占位符代表整个输入行,因此,对于每个输入行,调用一次rm,但正确处理带有嵌入空格的文件名。
    • 在所有情况下,--都确保不会将-开头的任何文件名都误认为rm的选项。

如果匹配文件需要单独处理或收集在shell数组中,则是原始问题的变体:

1
2
3
4
5
6
7
8
9
10
11
12
# 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=$'
'
read -d '' -ra files  < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s
'
"${files[@]}" # print array elements

删除目录中5个(或任意数量)的最新文件。

1
rm `ls -t | awk 'NR>5'`


1
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm

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

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

thelsdj答案的简单变体:

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

ls -tr显示所有文件,从最旧的开始(-t最新的在先,-r反向)。

head -n -5显示除最后5行(即5个最新文件)以外的所有内容。

xargs rm为每个选定文件调用rm。


1
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

对于-printf需要GNU查找,对于-z需要GNU排序,对于" 0"需要GNU awk,对于-0需要GNU xargs,但是需要处理带有嵌入式换行符或空格的文件。


当前目录中有目录时,所有这些答案均失败。这是可行的:

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

这个:

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

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

  • 当当前目录中的文件数量过多并且xargs通常会使您烦恼(-x)时,安全失败

  • 不能满足文件名中的空格(也许您使用的是错误的OS?)


  • 1
    ls -tQ | tail -n+4 | xargs rm

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

    在mklement0的有用注释后进行编辑(谢谢!):更正了-n + 3参数,请注意,如果文件名包含换行符和/或目录包含子目录,则此操作将无法按预期进行。


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

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

    如果文件名没有空格,则可以使用:

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

    如果文件名中确实包含空格,则类似

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

    基本逻辑:

    • 按时间顺序列出文件列表
    • 得到除前5个之外的所有字符(此示例为n = 5)
    • 第一版:将其发送给rm
    • 第二个版本:生成脚本,将其正确删除

    用zsh

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

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

    *(.om[6,999])中,.表示文件,o表示排序顺序,m表示按修改日期(将a用于访问时间,将c用于inode更改),选择文件范围,因此不要先保留5。


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

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

    这比以前的某些答案更可靠,因为它可以将搜索域限制为匹配表达式的文件。首先,找到符合您所需条件的文件。打印带有时间戳的文件。

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

    接下来,按时间戳对其进行排序:

    1
    sort -r -z -n

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

    1
    tail -n+5

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

    1
    awk '{ print $2; }'

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

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

    这可能是一个更冗长的命令,但是我能以条件文件为目标并针对它们执行更复杂的命令要好得多。


    我需要一个用于busybox(路由器)的优雅解决方案,所有xargs或阵列解决方案对我来说都是无用的-那里没有这样的命令。 find和mtime不是正确的答案,因为我们谈论的是10个项目,不一定是10天。埃斯波的答案是最短,最简洁,也可能是最普遍的答案。

    空格错误以及不删除任何文件时都可以通过标准方式解决:

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

    更具教育意义的版本:如果我们以不同的方式使用awk,则可以完成所有操作。通常,我使用这种方法将变量从awk传递(返回)到sh。当我们阅读所有无法完成的时间时,我要有所不同:这是方法。

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

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

    说明:

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

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

    print"rm \"" $0"\""构造一行:rm"文件名"

    eval执行它

    由于我们使用的是rm,因此我不会在脚本中使用以上命令! Wiser用法是:

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

    在使用ls -t命令的情况下,不会对诸如touch 'foo" bar'touch 'hello * world'这样的愚蠢示例造成任何损害。并不是说我们曾经在现实生活中使用此类名称创建文件!

    边注。如果我们想以这种方式将变量传递给sh,我们只需修改打印(简单形式,不能容忍空格):

    1
    print"VarName="$1

    将变量VarName设置为$1的值。可以一次创建多个变量。此VarName成为普通的sh变量,之后可以在脚本或shell中正常使用。因此,要使用awk创建变量并将其返回给shell,请执行以下操作:

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

    在Sed-Onliners中发现了有趣的cmd-删除最后3行-发现它是另一种为猫皮的方法的完美选择(可以),但可以这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     #!/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

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

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

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

    用bash计数文件


    我将其制作为bash shell脚本。用法:keep NUM DIR,其中NUM是要保留的文件数,DIR是要清理的目录。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #!/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
    2
    3
    4
    5
    6
    7
    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


    展开全文阅读

    相关内容