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

时间:2008-08-25 08:37:03

标签: bash unix scripting

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

为了给出更多具体示例,假设某个cron作业每小时将一个文件(例如,日志文件或tar-up备份)写入目录。我想要一种方法来运行另一个cron作业,它将删除该目录中最旧的文件,直到少于5个。

为了清楚起见,只有一个文件存在,它永远不会被删除。

17 个答案:

答案 0 :(得分:92)

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

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

答案 1 :(得分:91)

现有答案的问题:

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

wnoise's answer解决了这些问题,但解决方案是 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 ),您可以使用-0来处理NUL - 首次将换行符转换为NUL0x0)个字符后的分隔输入,这些字符也会(通常)同时传递所有文件名 (也可以使用GNU xargs):

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

<强>解释

  • ls -tp打印文件系统项目的名称,按照最近修改的方式排序,按降序排列(最近修改的项目)(-t),目录打印为尾随{{1}将它们标记为(/)。
  • -p然后通过省略(grep -v '/$')具有尾随-v/)的行来清除结果列表中的目录。
    • 警告:由于指向目录的符号链接在技术上本身并不是一个目录,因此将排除这些符号链接。
  • /$会跳过列表中的第一个 5 条目,实际上会返回所有最近修改的5个文件(如果有的话)。
    请注意,要排除tail -n +6个文件,必须将N传递给N+1
  • tail -n +(及其变体)然后在xargs -I {} rm -- {}上调用所有这些文件;如果根本没有匹配,rm将不会做任何事情。
    • xargs定义占位符xargs -I {} rm -- {},它代表每个输入行作为一个整体,因此{}会为每个输入行调用一次,但文件名为嵌入空间处理正确。
    • rm在所有情况下都会确保---开头的所有文件名都不会被rm误认为是选项

原始问题的变体以防需要处理匹配文件单独收集在shell数组中

# 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

答案 2 :(得分:85)

(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

答案 3 :(得分:58)

thelsdj答案的简单变体:

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

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

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

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

答案 4 :(得分:16)

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 find for -printf,GNU sort for -z,GNU awk for“\ 0”,GNU xargs for -0,但处理带有嵌入换行符或空格的文件。

答案 5 :(得分:13)

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

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

此:

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

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

  3. 当前目录中的文件数量过多而xargs通常会让您失意(-x

  4. 时安全失败
  5. 不满足文件名中的空格(也许你使用的是错误的操作系统?)

答案 6 :(得分:12)

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

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

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

答案 7 :(得分:8)

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

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

答案 8 :(得分:4)

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

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
  • 第二个版本:生成一个可以正确删除它们的脚本

答案 9 :(得分:2)

使用zsh

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

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

*(.om[6,999])中,.表示文件,o表示排序顺序,m表示按修改日期(表示a表示访问权限对于inode更改的时间或c[6,999]选择一个文件范围,因此不会首先记录5。

答案 10 :(得分:2)

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

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

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

答案 11 :(得分:1)

在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

答案 12 :(得分:1)

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

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

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

To count files with bash

答案 13 :(得分:1)

我需要一个用于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 -t命令的情况下,不会对诸如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"

答案 14 :(得分:0)

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

答案 15 :(得分:0)

我把它变成了一个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

答案 16 :(得分:-4)

在Debian上运行(假设我得到的其他发行版也一样: rm:无法删除目录`..'

这很烦人..

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

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

ls -C1 -t | grep文件| awk'NR&gt; 5'| xargs rm

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