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

bash unix scripting

93789 观看

16回复

70302 作者的声誉

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

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

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

作者: Matt Sheppard 的来源 发布者: 2008 年 8 月 25 日

回应 (16)


94

35222 作者的声誉

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

rm `ls -t | awk 'NR>5'`
作者: Espo 发布者: 25.08.2008 08:41

85

6134 作者的声誉

(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
作者: thelsdj 发布者: 25.08.2008 08:42

4

182959 作者的声誉

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

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
  • 第二个版本:生成脚本,将其正确删除
作者: Mark Harrison 发布者: 25.08.2008 08:43

13

0 作者的声誉

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

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

这个:

  1. 当前目录中有目录时有效

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

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

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

作者: Jon 发布者: 18.11.2008 05:44

16

8865 作者的声誉

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,但是需要处理带有嵌入式换行符或空格的文件。

作者: wnoise 发布者: 18.11.2008 07:51

8

5533 作者的声誉

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

while IFS= read -rd ''; do 
    x+=("${REPLY#* }"); 
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )
作者: Ian Kelling 发布者: 13.06.2009 12:03

2

5117 作者的声誉

用zsh

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

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

在中*(.om[6,999]).均值文件,o均值排序顺序,m均值按修改日期(a用于访问时间或c用于inode更改),[6,999]选择文件范围,因此不先管理5。

作者: lolesque 发布者: 21.11.2011 06:08

58

3495 作者的声誉

thelsdj答案的简单变体:

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

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

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

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

作者: Fabien 发布者: 12.04.2012 08:25

0

51 作者的声誉

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
作者: Pavel Tankov 发布者: 07.06.2013 07:34

12

4257 作者的声誉

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

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

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

作者: Mark 发布者: 25.07.2013 05:51

94

159375 作者的声誉

决定

现有答案存在的问题:

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

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

这是一个实用的,符合POSIX的解决方案,只有一个警告:它不能处理带有嵌入式换行符的文件名-但我认为大多数人都不关心现实世界。

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

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

上面的方法效率低下,因为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上的BSD),则可以在第一次将换行符转换为()字符之后,-0用来处理- NUL分隔的输入,这也一次(通常)传递所有文件名(也将与GNU一起使用):NUL0x0xargs

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

说明:

  • 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

变化上的原始问题,在情况下,匹配的文件需要被处理单独收集在壳阵列

# 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
作者: mklement0 发布者: 18.01.2016 07:24

0

341 作者的声誉

我将其制作为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
作者: Bulrush 发布者: 03.02.2016 05:47

1

11 作者的声誉

在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
作者: tim 发布者: 01.09.2016 09:09

2

372 作者的声誉

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

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

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

awk '{ print $2; }'

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

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

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

作者: TopherGopher 发布者: 10.01.2017 10:55

1

11 作者的声誉

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

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

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

用bash计数文件

作者: fabrice 发布者: 29.05.2017 05:14

1

64 作者的声誉

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

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

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“文件名”

eval 执行它

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

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

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

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

print "VarName="$1

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

eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\""  }'); echo "$VarName"
作者: Pila 发布者: 03.10.2018 05:48
32x32