如何在Bash中同时删除多个文件
1. 概述
当我们在 Linux 命令行下工作时,删除文件是一个标准的操作。
让我们想象一个典型的场景,我们有一个目录,其中包含一堆文件名相似的文件,我们想根据不同的要求删除其中的一些文件。
在本教程中,我们将学习如何从 Linux 命令行一次性删除多个文件。
2. 问题介绍
首先,我们将创建一个示例文件列表。
假设我们有一个名为logs的目录:
$ ls -1 logs
app.log.2020-06-15
app.log.2020-06-21
app.log.2020-06-22
app.log.2020-06-23
app.log.2020-06-24
app.log.2020-06-25
app.log.2020-07-15
app.log.2020-07-25
app.log.2020-07-26
app.log.2020-07-27
app.log.2020-07-28
app.log.2020-08-25
app.log.2020-08-26
app.log.2020-08-27
app.log.2020-08-28
app.log.2020-08-29
app.log.2020-08-30
app.log.2020-08-31
如上面的输出所示,我们在 logs目录中有许多日志文件。每个文件名都以日期结尾。
我们将讨论四种有效删除多个文件的不同方法:
3. 使用 Bash 的大括号扩展
如果我们知道要删除的确切文件名,并且文件名遵循相同的模式,我们可以考虑使用大括号扩展来节省大量输入并使命令紧凑且更易读。
假设我们要从日志目录中删除2020-08-25 、 2020-08-27 、 2020-08-30和2020-08-31的log文件。 当然,我们可以在 rm命令后输入四个文件名:
$ rm logs/app.log.2020-08-25 logs/app.log.2020-08-27 logs/app.log.2020-08-30 logs/app.log.2020-08-31
通常,我们的 shell 可以自动完成文件名。它节省了很多打字。但是,从一长串非常相似的文件名中选择文件很容易出错。除此之外,长命令不容易阅读或检查。
在这种情况下,大括号扩展不仅可以节省我们的输入,还可以使命令紧凑和可读:
$ rm logs/app.log.2020-08-{25,27,30,31}
但是,如果我们不确切知道要删除的文件名,大括号扩展对我们没有帮助——例如:“删除 2020 年 7 月的所有日志文件”。
如果是这种情况,我们可以考虑使用文件通配符或正则表达式来匹配我们的目标文件。
4. 使用 Bash 的文件 Globbing
glob 有时称为通配符。在我们的日常工作中,我们经常使用 glob。例如,“ *.java ”表示所有 Java 源文件。
如果我们要删除“ app ”应用的所有日志文件,可以执行:
$ rm logs/app.log.*
或者我们可以删除 2020 年 7 月以来的所有日志文件:
$ rm logs/app.log.2020-07-*
使用 globbing,我们可以方便地将多个文件与名称模式匹配。
在我们通过rm命令使用文件 globbing 之前,最好使用ls命令测试 glob 以检查匹配文件列表是否是我们要删除的:
$ ls -1 logs/app.log.2020-07-*
logs/app.log.2020-07-15
logs/app.log.2020-07-25
logs/app.log.2020-07-26
logs/app.log.2020-07-27
logs/app.log.2020-07-28
5. 使用find命令
文件通配符是匹配多个文件的便捷方式。但是,正则表达式(regex)在模式匹配方面更为强大。
5.1. 查找要删除的文件
假设我们有一个新需求:我们检查 6 月和 7 月的日志文件。如果月份中的第几天包含数字“ 1 ”,我们应该删除该文件,如“ 10 ”、“ 01 ”、“ 21 ”等。
find命令 具有 -regex 模式选项来过滤与给定正则表达式匹配的文件名。因此,我们可以转向find命令来查找我们要删除的文件:
$ find logs/ -regex '.*0[67]-\(1.\|.1\)$'
logs/app.log.2020-06-15
logs/app.log.2020-07-15
logs/app.log.2020-06-21
值得一提的是,在我们的日志目录中,我们没有任何子目录。否则,我们可能需要在find命令中添加两个附加选项:“ -maxdepth 1 -type f”。这两个选项告诉 find命令只搜索logs目录下的文件。
5.2. 删除找到的文件
一旦我们有了要删除的文件,对我们执行实际的删除操作就不难了。我们有几种方法可以删除我们找到的文件。 我们先来看看使用find命令的*-exec动作对找到的文件执行rm*命令:
$ find logs/ -regex '.*0[67]-\(1.\|.1\)$' -exec rm "{}" \;
另外,我们可以通过/xargs 命令删除find命令找到的文件:
$ find logs/ -regex '.*0[67]-\(1.\|.1\)$' | xargs -I{} rm "{}"
find 命令还支持*-delete操作来删除*匹配的文件:
$ find logs/ -regex '.*0[67]-\(1.\|.1\)$' -delete
无论我们要应用哪种删除方法,在使用删除操作之前检查find结果始终是一个好习惯。
6. 使用awk命令
到目前为止,我们已经看到了大括号扩展、文件通配和正则表达式的威力。使用这些方法,我们可以解决大多数批处理文件删除问题。
但是,当删除需求不限于模式匹配时,上述三种技术可能无法有效解决问题。
在本节中,我们将看到几个新问题:
- 问题一:删除某个日期范围内的日志文件
- 问题 2:删除超过n天的日志文件 强大的 awk命令将帮助我们解决这些问题。
6.1. 删除日期范围内的日志文件
假设我们要删除2020-06-23和2020-08-29之间的日志文件。
使用我们目前学到的模式匹配技术,查找日期范围内的日志文件并不容易。但是使用awk命令获取这些文件很简单:
$ awk -F'.' -v from='2020-06-23' -v to='2020-08-29' '$3>=from && $3<=to' <(ls -1 logs/*)
logs/app.log.2020-06-23
logs/app.log.2020-06-24
logs/app.log.2020-06-25
logs/app.log.2020-07-25
logs/app.log.2020-07-26
logs/app.log.2020-07-27
logs/app.log.2020-07-28
logs/app.log.2020-08-25
logs/app.log.2020-08-26
logs/app.log.2020-08-27
logs/app.log.2020-08-28
logs/app.log.2020-08-29
如上面的输出所示, awk命令找到了2020-06-23和2020-08-29之间的日志文件。
现在,让我们仔细看看 awk命令并了解它是如何工作的:
- <(ls -1 logs) :在这里,我们使用进程替换来提供 awk命令。命令 ls -1 logs/*的输出成为awk命令 的输入
- -F ‘。’ -v from=‘2020-06-23’ -v to=‘2020-08-29’:我们使用句点(“ . ”)作为字段分隔符 ,以便我们可以轻松提取日期字段($3)。此外,我们声明两个变量来存储给定日期范围的边界
- ’$3>=from && $3<=to’:这很容易理解——我们获取时间范围内的日志文件并打印文件名
在我们有了要删除的文件之后,下一步就是对文件执行实际的删除操作。
我们学会了使用find | xargs组合删除 find命令找到的文件。
同样,我们也可以将awk命令的输出传递给 xargs和rm:
$ awk -v from='2020-06-23' -v to='2020-08-29' -F'.' '$3>=from && $3<=to' <(ls -1 logs/*) | xargs -I{} rm "{}"
$ ls -1 logs
app.log.2020-06-22
app.log.2020-08-30
app.log.2020-08-31
结果显示,两个给定日期之间的日志文件已被删除。
6.2. 删除超过给定天数的日志文件
假设我们要删除超过 35 天的日志文件。
在展示 awk命令之前,让我们先看一下用于计算35 天前日期的date 命令:
# the current date:
$ date +%F
2020-08-31
# 35 days ago
$ date +%F -d '35 days ago'
2020-07-27
一旦我们有了 35 天前的日期,这个问题就会变成“删除早于2020-07-27 的日志文件”。让我们看看 awk命令如何一次找到这些文件:
$ awk -F'.' -v dt="$(date +%F -d '35 days ago')" '$3 <= dt' <(ls -1 logs/*)
logs/app.log.2020-06-22
logs/app.log.2020-06-23
logs/app.log.2020-06-24
logs/app.log.2020-06-25
logs/app.log.2020-07-25
logs/app.log.2020-07-26
logs/app.log.2020-07-27
我们使用命令替换通过 date命令的输出 分配变量dt。剩下的工作就变得简单了。我们只列出日期早于或等于变量dt 值的日志文件。
接下来,让我们删除找到的文件。
我们知道我们可以将awk命令的输出通过管道传输 到“ xargs rm ”以删除文件。或者,我们可以要求 awk命令构建rm命令并将输出通过管道传输到sh以执行。整个命令看起来像:awk ‘…codes to build rm cmds…’ input | sh。
让我们使用这种技术来解决这个问题。首先,让我们构建rm命令:
$ awk -F'.' -v dt="$(date +%F -d '35 days ago')" -v rm_cmd='rm "%s"\n' '$3 <= dt{printf rm_cmd,$0} ' <(ls -1 logs/*)
rm "logs/app.log.2020-06-15"
rm "logs/app.log.2020-06-21"
rm "logs/app.log.2020-06-22"
rm "logs/app.log.2020-06-23"
rm "logs/app.log.2020-06-24"
rm "logs/app.log.2020-06-25"
rm "logs/app.log.2020-07-15"
rm "logs/app.log.2020-07-25"
rm "logs/app.log.2020-07-26"
rm "logs/app.log.2020-07-27"
如果生成的命令看起来不错,我们将它们通过管道传递给sh以进行实际删除:
$ awk -F'.' -v dt="$(date +%F -d '35 days ago')" -v rm_cmd='rm "%s"\n' '$3 <= dt{printf rm_cmd,$0}' <(ls -1 logs/*) | sh
$ ls -1 logs
app.log.2020-07-28
app.log.2020-08-25
app.log.2020-08-26
app.log.2020-08-27
app.log.2020-08-28
app.log.2020-08-29
app.log.2020-08-30
app.log.2020-08-31
因此,所有超过 35 天的日志文件都已被删除。