删除 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 为当前目录中的每个文件发出<mtime> <filename><NUL>
。
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; < <(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 个文件之外的所有文件的主要内容,如果未能解决你的问题,请参考以下文章
Linux Centos 删除除某(多)个文件之外的所有文件