删除文件中列出的文件
1. 概述
当我们在 Linux 命令行下工作时,我们经常需要对文件进行操作。删除文件是一种常见的操作。 我们可能会面临不同的文件删除需求——例如,删除超过给定时间的 文件、递归删除具有特定扩展名的 文件、一次删除多个文件 等等。
这一次,我们将讨论如何删除另一个文件中列出的文件。
2. 问题介绍
2.1. 问题的例子
一个例子可以更容易地解释这个问题。 首先,我们准备了一个目录 delTest和它下面的一些文件:
$ tree /tmp/delTest/
/tmp/delTest/
├── jpg_files
│ ├── olympic tokyo 001.jpg
│ ├── olympic tokyo 002.jpg
│ └── olympic tokyo 003.jpg
├── pdf_files
│ ├── bar.pdf
│ └── foo.pdf
├── toDelete.txt
└── txt_files
├── bar.txt
└── foo.txt
3 directories, 8 files
我们使用tree 命令更清晰地输出目录结构。请注意,jpg_files目录下的文件名包含空格。
假设我们在toDelete.txt文件中定义了要删除的文件:
$ cat toDelete.txt
/tmp/delTest/pdf_files/foo.pdf
/tmp/delTest/txt_files
/tmp/delTest/jpg_files/olympic tokyo 001.jpg
我们的目标是删除toDelete.txt文件中列出的文件。
2.2. 解决问题的思路
由于toDelete.txt文件中已经定义了我们要删除的文件和目录,所以我们有两个思路来解决这个问题:
- 读取toDelete.txt文件中的每一行,并对每一行执行rm命令以删除该文件。
- 读取toDelete.txt文件的内容并将这些行转换为一堆rm命令。最后,将结果通过管道传递给sh命令以执行它们。
我们将介绍三种方法来涵盖这两种想法:
现在,让我们看看他们的行动。
3. 使用纯 Bash
今天,Bash 已成为大多数现代 Linux 发行版的默认 shell。所以, 如果我们用纯 Bash 解决了一个问题,也就是说,我们的解决方案不依赖任何额外的依赖。
3.1. 处理目录
如示例所示,问题看起来很简单。但是,该问题可能有一些变体。现在,让我们仔细看看它们。
toDelete.txt文件中列出的 文件可能包含目录。例如,/tmp/delTest/txt_files是一个目录。
根据要求,我们可能希望跳过删除目录或递归删除目录。
rm命令提供了一个*-r 选项。此选项允许我们以递归方式删除目录。此外,**如果我们对常规文件执行rm -r*,它也会删除该文件**。
因此,如果需求是删除 toDelete.txt文件中的文件和目录,我们可以对文件中的行执行 rm -r。否则,我们调用 不带 -r选项的rm来跳过删除目录。
在本教程中,假设要求是删除目录和文件。也就是说,我们将使用 带有*-r选项的**rm*作为我们的文件删除命令。
3.2. 引用文件名
大多数 Linux 文件系统都接受文件名中的空格。例如,文件*/tmp/delTest/jpg_files/olympic tokyo 001.jpg*包含多个空格。
当我们通过文件名操作文件时,引用文件名是一个好习惯。或者我们可能会看到意想不到的结果。
但是,值得一提的是,引用文件名将禁用 glob 扩展,无论我们使用单引号还是双引号。
我们还假设支持 glob 不是这个问题的要求。这是因为如果toDelete.txt文件中有一些错误,在现实世界中可能会很危险。例如,如果我们在/path/*/*上运行rm -r ,它会删除很多文件。
因此,我们将始终在所有解决方案中引用文件名。
现在,让我们编写一个简单的 shell 脚本来读取toDelete.txt文件并删除这些文件。
3.3. 简单的 Bash 脚本
首先,让我们看一下脚本:
$ cat delFromFile.sh
#!/bin/bash
TO_BE_DEL="$1"
IFS=""
while read -r file ; do
rm -r "$file"
done < "$TO_BE_DEL"
剧本不难理解。它只包含一个while循环。让我们快速通过它。
首先,我们将IFS 变量设置为空,以便toDelete.txt中的每一行在我们read 时都成为一条记录。
在while循环中,我们 对toDelete.txt文件中定义的每个目录或文件 执行rm -r命令。
接下来,让我们测试一下脚本,看看它是否像我们预期的那样工作:
$ ./delFromFile.sh /tmp/delTest/toDelete.txt
$ tree /tmp/delTest
/tmp/delTest
├── jpg_files
│ ├── olympic tokyo 002.jpg
│ └── olympic tokyo 003.jpg
├── pdf_files
│ └── bar.pdf
└── toDelete.txt
2 directories, 4 files
执行完脚本后,我们再次调用tree命令来验证结果。我们看到我们已经成功删除了toDelete.txt 中列出的目录和文件。
因此,我们解决了这个问题。
4. 使用xargs命令
** xargs命令从stdin读取输入并将其转换为参数以提供给其他命令 。**
现在,让我们看一下用于删除toDelete.txt文件中列出的文件的单行xargs命令:
xargs -I{} rm -r "{}" < /tmp/delTest/toDelete.txt
由于 xargs命令仅从stdin读取,我们将输入文件toDelete.txt重定向到stdin。此外,我们定义了一个占位符“{}”,以便我们可以在 rm命令中引用文件名。
接下来,让我们恢复 /tmp/delTest目录下的文件并测试这个命令:
$ xargs -I{} rm -r "{}" </tmp/delTest/toDelete.txt
$ tree /tmp/delTest
/tmp/delTest/
├── jpg_files
│ ├── olympic tokyo 002.jpg
│ └── olympic tokyo 003.jpg
├── pdf_files
│ └── bar.pdf
└── toDelete.txt
2 directories, 4 files
该命令按我们的预期工作。
5. 使用sed命令
到目前为止,我们已经了解了如何使用纯 Bash 和xargs来解决问题。这两种方法都从 toDelete.txt文件中获取每一行并提供rm -r命令。
我们可以按照另一种思路,将文件内容转换成多个 rm命令。
sed命令可以使用紧凑的 单线轻松处理此类任务:
$ sed 's/.*/rm -r "\0"/' /tmp/delTest/toDelete.txt
rm -r "/tmp/delTest/pdf_files/foo.pdf"
rm -r "/tmp/delTest/txt_files"
rm -r "/tmp/delTest/jpg_files/olympic tokyo 001.jpg"
如上面的输出所示, sed 命令行通过替换构建了我们需要的rm命令。此外,文件名也被正确引用。
虽然sed命令本身不会执行实际删除,但我们有机会检查它产生的rm命令。因此,它可以帮助我们发现错误。
如果命令看起来不错,我们可以将 sed 命令行的结果通过管道传输到 sh以删除文件。
接下来,让我们恢复文件并测试我们的 sed 命令行:
$ sed 's/.*/rm -r "\0"/' /tmp/delTest/toDelete.txt | sh
$ tree /tmp/delTest
/tmp/delTest/
├── jpg_files
│ ├── olympic tokyo 002.jpg
│ └── olympic tokyo 003.jpg
├── pdf_files
│ └── bar.pdf
└── toDelete.txt
2 directories, 4 files
6. 使用 awk命令
从toDelete.txt文件构建rm命令对于awk来说是小菜一碟。同样,我们也可以使用awk的替换函数来完成这项工作。
但是,在这里,我们展示了另一种方法:
$ awk -v q='"' '$0 = "rm -r " q $0 q' /tmp/delTest/toDelete.txt
rm -r "/tmp/delTest/pdf_files/foo.pdf"
rm -r "/tmp/delTest/txt_files"
rm -r "/tmp/delTest/jpg_files/olympic tokyo 001.jpg"
让我们快速了解一下简短的 awk命令是如何工作的。
为了避免转义引号字符并使代码更易于阅读,我们声明了一个awk变量 q来存储双引号字符。
我们知道,在我们将“ rm -r ”和引号连接到原始输入行之后,结果一定不能是空字符串。
** awk将评估任何非空和非零字符串为True。此外,语句*“True”将触发默认操作:打印当前处理行。*
因此,awk打印生成的rm命令。
与 sed解决方案相同,如果我们想要进行实际删除,只需将 awk输出通过管道传输到 sh:
$ awk -v q='"' '$0="rm -r " q $0 q' /tmp/delTest/toDelete.txt | sh
$ tree /tmp/delTest
/tmp/delTest/
├── jpg_files
│ ├── olympic tokyo 002.jpg
│ └── olympic tokyo 003.jpg
├── pdf_files
│ └── bar.pdf
└── toDelete.txt
2 directories, 4 files