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

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

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

有帮助吗?

解决方案

现有答案的问题:

  • 无法处理带有嵌入空格或换行符的文件名。
    • 在解决方案调用的情况下 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 (包括在 操作系统), 您可以使用 -0 处理 NUL- 分隔输入,首先将换行符翻译为 NUL (0x0) chars.,它也传递(通常)所有文件名 立刻 (也将与 GNU 一起工作 xargs):

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.

A 变化 在原来的问题上, 如果需要处理匹配的文件 单独地 或者 收集在 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

其他提示

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

rm `ls -t | awk 'NR>5'`
(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 答案的更简单变体:

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

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

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

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

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

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

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

这:

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

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

  3. 当当前目录中的文件数量过多时,安全失败 xargs 通常会把你搞砸( -x)

  4. 不支持文件名中的空格(也许您使用了错误的操作系统?)

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

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

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

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

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

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

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

使用 zsh

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

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

*(.om[6,999]), , 这 . 表示文件, o 表示向上排序, m 表示按修改日期(将 a 访问时间或 c 对于 inode 更改), [6,999] 选择文件范围,因此首先不选择 5。

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

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

这可能是一个更详细的命令,但我的运气要好得多,能够定位条件文件并针对它们执行更复杂的命令。

在 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

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

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

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

使用 bash 对文件进行计数

我需要一个针对 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, ,我不会在脚本中使用上面的命令!更明智的用法是:

(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"
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

我把它做成了 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

在 Debian 上运行(假设它在我得到的其他发行版上是一样的:R M:无法删除目录“..”

这很烦人..

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

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

LS -C1 -T | GREP文件| awk'nr> 5'| xargs rm

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top