删除 bash 脚本中除了最新的 3 个文件之外的所有文件

Posted

技术标签:

【中文标题】删除 bash 脚本中除了最新的 3 个文件之外的所有文件【英文标题】:Delete all files except the newest 3 in bash script 【发布时间】:2015-01-02 02:45:39 【问题描述】:

问题:如何删除目录中除最新的3个以外的所有文件?

查找最新的 3 个文件很简单:

ls -t | head -3

但我需要找到除了最新的 3 个文件之外的所有文件。我该怎么做,如何在同一行中删除这些文件而不为此创建不必要的 for 循环?

我为此使用 Debian Wheezy 和 bash 脚本。

【问题讨论】:

ls 实际上是适合这项工作的错误工具——请参阅mywiki.wooledge.org/ParsingLs。如果你有 GNU find,你可以用一个 -printf 格式字符串做得更好,它有时间戳(最好是sort -n -z 的 UNIX 时间),一个分隔符,然后是一个 NUL;这样即使是带有换行符的文件名也不会把它扔掉。 我也不同意在这里使用循环是不必要的。正确而稳健地做事与简洁地做事不同,但其他任何事情都是......嗯......不正确。 【参考方案1】:

这将列出除最新的三个以外的所有文件:

ls -t | tail -n +4

这将删除这些文件:

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

这也会列出点文件:

ls -At | tail -n +4

并使用点文件删除:

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

但请注意:当文件名包含有趣的字符(如换行符或空格)时,解析 ls 可能会很危险。如果您确定您的文件名不包含有趣的字符,那么解析 ls 是非常安全的,如果它是一次性脚本更是如此。

如果您正在开发一个重复使用的脚本,那么您绝对不应该解析ls 的输出并使用此处描述的方法:http://mywiki.wooledge.org/ParsingLs

【讨论】:

@DevilsChild:取决于您是否关心是否正确。如果您不在乎,只需通过管道传输到 xargs...但如果有任何重要的事情(例如备份脚本),请不要永远这样做。 @DevilsChild 我确实看到 TB 的备份被删除,因为缓冲区溢出创建了一个名称中包含垃圾的文件,并且有人认为,由于文件名的创建是程序化的,因此永远不会发生不寻常的名称。走捷径会让你很难受。 rm 命令中双破折号-- 的作用是什么? 它可以防止文件名以破折号或减号开头。 unix.stackexchange.com/questions/1519/… 非常优雅的解决方案:)【参考方案2】:

以下内容看起来有点复杂,但要非常谨慎才能正确,即使是不寻常或故意恶意的文件名。不幸的是,它需要 GNU 工具:

count=0
while IFS= read -r -d ' ' && IFS= read -r -d '' filename; do
  (( ++count > 3 )) && printf '%s\0' "$filename"
done < <(find . -maxdepth 1 -type f -printf '%T@ %P\0' | sort -g -z) \
     | xargs -0 rm -f --

解释这是如何工作的:

Find 为当前目录中的每个文件发出 &lt;mtime&gt; &lt;filename&gt;&lt;NUL&gt;sort -g -z 基于第一列(次)进行一般(浮点,而不是整数)数字排序,行由 NUL 分隔。 while 循环中的第一个 read 会剥离 mtime(sort 完成后不再需要)。 while 循环中的第二个 read 读取文件名(运行到 NUL)。 循环递增,然后检查计数器;如果计数器的状态表明我们已经过了最初的跳过,那么我们打印文件名,由 NUL 分隔。 xargs -0 然后将该文件名附加到它正在收集的 argv 列表中以调用 rm

【讨论】:

你错过了 xargs 中的-0 吗?此外,您可以使用 group 和 dummy read 跳过前三个: read; read; read; while ... done; &lt; &lt;(find ...) 这将避免需要计数器。 @gniourf_gniourf,是的,但它们需要用-d '' 进行虚拟读取,这使得它们足够长,以至于我去柜台。 -0 的好消息;我只在那之前测试过。 @gniourf_gniourf,唯一的原因是为了避免外部命令调用的次数,每次调用rm 一次 MAX_ARGV 填满比每次调用一次要快。很好地抓住了失踪的IFS= 也许颠倒排序?最新的文件会有更大的日期,所以顺序必须是降序的(因为我们将前三个保留在该列表中)。 “不起作用”有点强——一般用例是亚秒级精度无关紧要的用例(在许多文件系统上,输入中甚至没有亚秒级精度数据开始)——但由于我们已经依赖于 GNU 工具,因此更改没有任何害处。【参考方案3】:
ls -t | tail -n +4 | xargs -I  rm 

如果你想要一个 1 班轮

【讨论】:

这对带有有趣名称的文件安全吗? 如果你的意思是中间有 * 的名字,我会说不。使用 find 命令找到它们。【参考方案4】:

这使用find 而不是ls 和Schwartzian transform。

find . -type f -printf '%T@\t%p\n' |
sort -t $'\t' -g |
tail -3 |
cut -d $'\t' -f 2-

find 搜索文件并用时间戳装饰它们,并使用制表符分隔两个值。 sort 通过制表符拆分输入并执行一般数字排序,从而正确排序浮点数。 tail 应该很明显,cut 不装饰。

一般来说,装饰的问题是要找到一个合适的分隔符,它不是输入文件名的一部分。此answer 使用 NULL 字符。

【讨论】:

【参考方案5】:

不要使用ls -t,因为它对于可能包含空格或特殊全局字符的文件名是不安全的。

您可以使用所有基于 gnu 的实用程序来删除当前目录中除 3 个最新文件之外的所有文件:

find . -maxdepth 1 -type f -printf '%T@\t%p\0' |
sort -z -nrk1 |
tail -z -n +4 |
cut -z -f2- |
xargs -0 rm -f --

【讨论】:

tail -z -n +4 将保留 2 个最新文件。不知道为什么我必须将 +2 添加到要保留的文件数中。 tail -z -n +4 将从第 4 行开始获取条目【参考方案6】:

“ls”(奇怪的命名文件)没有问题的解决方案

这是ceving 和anubhava 的答案的组合。 两种解决方案都不适合我。因为我正在寻找一个应该每天运行的脚本来备份存档中的文件,所以我想避免ls 出现问题(有人可以在我的备份文件夹中保存一些有趣的命名文件)。所以我修改了上述解决方案以满足我的需求。

我的解决方案会删除所有文件,除了三个最新文件。

find . -type f -printf '%T@\t%p\n' |
sort -t $'\t' -g | 
head -n -3 | 
cut -d $'\t' -f 2- |
xargs rm

一些解释:

find 列出当前文件夹中的所有文件(不是目录)。它们与时间戳一起打印出来。sort 根据时间戳对行进行排序(最旧的在顶部)。head 打印出最上面的行,直到最后 3 行。cut删除时间戳。xargs 为每个选定的文件运行 rm

供您验证我的解决方案:

(
touch -d "6 days ago" test_6_days_old
touch -d "7 days ago" test_7_days_old
touch -d "8 days ago" test_8_days_old
touch -d "9 days ago" test_9_days_old
touch -d "10 days ago" test_10_days_old
)

这会在当前文件夹中创建 5 个具有不同时间戳的文件。先运行这个脚本,再运行删除旧文件的代码。

【讨论】:

对我的剧本很有魅力! 我喜欢这个脚本,但是当过滤器没有捕获任何文件时会发生什么?在我有限的测试中,rm 以非零值退出。作为测试,运行上面的文件创建,然后运行脚本,但在head 命令中使用-8 的值。这不返回任何值,这使得rm 退出并出现错误。但是,rm 上的 -f 标志似乎会使其以 0 退出,即使没有结果也是如此。所以最后一行可以修改为xargs rm -f,如果你需要脚本在没有结果的情况下干净地退出。 您也可以将“-r”选项添加到 xargs 以处理这种情况。【参考方案7】:

在 zsh 中:

rm /files/to/delete/*(Om[1,-4])

如果您想包含 dotfiles,请将括号内的部分替换为 (Om[1,-4]D)

我认为这适用于文件名中的任意字符(只需用换行符检查)。

说明:括号中包含全局限定符。 O 表示“排序,降序”,m 表示 mtime(有关其他排序键,请参见 man zshexpn - 大型联机帮助页;搜索“被排序”)。 [1,-4] 仅返回从 1 开始的索引 1 到 (last + 1 - 4) 的匹配项(注意 -4 用于删除除 3 之外的所有内容)。

【讨论】:

【参考方案8】:
ls -t | tail -n +4 | xargs -I  rm 

Michael Ballent 的回答最适合

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

如果我的文件少于 3 个,则抛出错误

【讨论】:

【参考方案9】:

作为answer by flohall 的扩展。如果要删除除最新的三个文件夹之外的所有文件夹,请使用以下命令:

find . -maxdepth 1 -mindepth 1 -type d -printf '%T@\t%p\n' |
 sort -t $'\t' -g | 
 head -n -3 | 
 cut -d $'\t' -f 2- |
 xargs rm -rf

-mindepth 1 将忽略父文件夹和 -maxdepth 1 子文件夹。

【讨论】:

【参考方案10】:

具有任意数量文件的递归脚本以保留每个目录

还处理带有空格、换行符和其他奇数字符的文件/目录

#!/bin/bash
if (( $# != 2 )); then
  echo "Usage: $0 </path/to/top-level/dir> <num files to keep per dir>"
  exit
fi

while IFS= read -r -d $'\0' dir; do
  # Find the nth oldest file
  nthOldest=$(find "$dir" -maxdepth 1 -type f -printf '%T@\0%p\n' | sort -t '\0' -rg \
    | awk -F '\0' -v num="$2" 'NR==num+1print $2')

  if [[ -f "$nthOldest" ]]; then
    find "$dir" -maxdepth 1 -type f ! -newer "$nthOldest" -exec rm  +
  fi
done < <(find "$1" -type d -print0)

概念证明

$ tree test/
test/
├── sub1
│   ├── sub1_0_days_old.txt
│   ├── sub1_1_days_old.txt
│   ├── sub1_2_days_old.txt
│   ├── sub1_3_days_old.txt
│   └── sub1\ 4\ days\ old\ with\ spaces.txt
├── sub2\ with\ spaces
│   ├── sub2_0_days_old.txt
│   ├── sub2_1_days_old.txt
│   ├── sub2_2_days_old.txt
│   └── sub2\ 3\ days\ old\ with\ spaces.txt
└── tld_0_days_old.txt

2 directories, 10 files
$ ./keepNewest.sh test/ 2
$ tree test/
test/
├── sub1
│   ├── sub1_0_days_old.txt
│   └── sub1_1_days_old.txt
├── sub2\ with\ spaces
│   ├── sub2_0_days_old.txt
│   └── sub2_1_days_old.txt
└── tld_0_days_old.txt

2 directories, 5 files

【讨论】:

【参考方案11】:

以下对我有用:

rm -rf $(ll -t | tail -n +5 | awk ' print $9')

【讨论】:

这有很多问题。 ll 不是标准命令,尽管在面向初学者的发行版中它经常别名为 ls -l;但是为什么要求一个长列表只是为了(不太成功)丢弃长列表提供的信息?即使对于不包含空格的文件名,这也存在parsing ls 的所有其他问题,当然,rm -rf 对于问题中的用例完全不正确,并且在这里可能非常危险。

以上是关于删除 bash 脚本中除了最新的 3 个文件之外的所有文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Ant 删除除最新的 2 个文件之外的所有文件

Linux 删除除了某个文件之外的所有文件(抄的)

删除最终的 bash 脚本参数

Linux Centos 删除除某(多)个文件之外的所有文件

linux删除文件夹下除了某一个文件之外的所有文件及find用法

日志文件删除shell脚本