从另一个文件 A 中删除文件 B 上出现的行
1. 概述
当我们在 Linux 命令行下工作时,操作文本文件是一种标准操作。
在本教程中,我们将讨论如何从另一个文件 A 中删除文件 B 中出现的行。
2. 问题介绍
让我们通过一个例子快速理解这个问题。
首先,让我们创建一个名为 books.txt的文本文件:
$ cat books.txt
PRIDE AND PREJUDICE - Austen,Jane
AROUND THE WORLD IN 80 DAYS - Verne, Jules
A TALE OF TWO CITIES - Dickens, Charles
LES MISERABLES - Hugo,Victor
ANNA KARENINA - Tolstoy, Leo
CRIME AND PUNISHMENT - Dostoevsky, Fyodor
THE COUNT OF MONTE CRISTO - Dumas, Alexandre, pere
JANE EYRE - Bronte, Charlotte
MADAME BOVARY - Flaubert,Gustave
THE HUNCHBACK OF NOTRE DAME - Hugo, Victor
*books.txt *包含十行。我们想从文件中删除一些行。我们在另一个文本文件中定义要删除的行:
$ cat delete_list.txt
MADAME BOVARY - Flaubert,Gustave
THE COUNT OF MONTE CRISTO - Dumas, Alexandre, pere
A TALE OF TWO CITIES - Dickens, Charles
THE HUNCHBACK OF NOTRE DAME - Hugo, Victor
在本教程中,我们将介绍几种解决问题的方法:
3. 使用comm和sort命令
我们可以使用 comm命令从两个输入文件中获取公共或唯一行。
如果我们 从books.txt中删除了delete_list.txt文件中的行,则结果与两个文件中的唯一行相同。
comm FILE1 FILE2命令的输出 包含三列:
- 1 – FILE1独有的行
- 2 – FILE2独有的行
- 3 – FILE1和FILE2中的行
如果我们想抑制结果中的一列,我们添加*-columnNumber*选项。例如,comm -12 FILE1 FILE2将抑制第 1 列和第 2 列。输出中将仅显示第 3 列。因此,我们将在两个输入文件中都有公共行的输出。
在我们的场景中,由于我们只对第一列感兴趣,我们可以使用comm命令来解决问题:
comm -23 books.txt delete_list.txt
但是,** comm 命令仅适用于已排序的输入文件。我们可以使用进程替换 将排序的输入文件传递给 comm命令:
$ comm -23 <(sort books.txt) <(sort delete_list.txt)
ANNA KARENINA - Tolstoy, Leo
AROUND THE WORLD IN 80 DAYS - Verne, Jules
CRIME AND PUNISHMENT - Dostoevsky, Fyodor
JANE EYRE - Bronte, Charlotte
LES MISERABLES - Hugo,Victor
PRIDE AND PREJUDICE - Austen,Jane
如果我们检查上面的输出, delete_list.txt中的行已被删除。所以我们已经使用comm命令解决了这个问题 。
4. 使用join和sort命令
** join命令可以通过给定字段从两个排序的输入文件中连接行。 此外,该命令提供了*-v FILENUM*选项来打印无法连接的行。**
在我们的示例中,books.txt文件中不可连接的行正是我们正在寻找的结果。
类似地,让我们将join和sort命令组合起来,就像我们在上面的comm解决方案中所做的那样:
$ join -v 1 <(sort books.txt) <(sort delete_list.txt)
ANNA KARENINA - Tolstoy, Leo
AROUND THE WORLD IN 80 DAYS - Verne, Jules
join: /proc/self/fd/11:3: is not sorted: A TALE OF TWO CITIES - Dickens, Charles
A TALE OF TWO CITIES - Dickens, Charles
CRIME AND PUNISHMENT - Dostoevsky, Fyodor
JANE EYRE - Bronte, Charlotte
LES MISERABLES - Hugo,Victor
PRIDE AND PREJUDICE - Austen,Jane
join: input is not in sorted order
哎呀!输出包含错误消息:
join: /proc/self/fd/11:3: is not sorted: A TALE OF TWO CITIES - Dickens, Charles
join: input is not in sorted order
但是我们确实在进程替换中对输入文件进行了排序。为什么join命令仍然抱怨这个?
这是组合sort和join命令的常见缺陷。让我们了解错误的原因。
**如果我们不定义任何键, sort命令将按整行对输入文件进行排序。**但是,**默认情况下, join命令将通过第一个字段连接两个输入文件。**默认字段分隔符为空白。
当我们一起使用 join 和sort命令时,我们应该确保这两个命令是通过相同的字段来连接和排序的。
让我们回到我们的问题。在这种情况下,我们希望按整行连接两个排序的文件并打印未连接的行。因此,我们需要使用*-t选项将换行符(\n*)设置为 连接命令的字段分隔符:
$ join -t $'\n' -v 1 <(sort books.txt) <(sort delete_list.txt)
ANNA KARENINA - Tolstoy, Leo
AROUND THE WORLD IN 80 DAYS - Verne, Jules
CRIME AND PUNISHMENT - Dostoevsky, Fyodor
JANE EYRE - Bronte, Charlotte
LES MISERABLES - Hugo,Victor
PRIDE AND PREJUDICE - Austen,Jane
伟大的!我们的问题解决了。
也许值得一提的是,当我们将换行符传递给 -t选项时,我们使用了ANSI-C 引用 的 $’\n’而不是’\n’。
这是因为 当我们传递多个字符时,不同的连接实现可能会有不同的行为。例如,如果我们将’\n’直接传递给 -t选项,广泛使用的GNU join会报错:“ join: multi-character tab ‘\n’ ”。
到目前为止,我们已经看到了解决问题的两种解决方案:comm解决方案和 join解决方案。它们都需要排序的输入文件。
此外,如果我们仔细查看我们的结果,我们会发现我们想要删除的行不再存在。这个很不错。但是,由于排序操作, ** books.txt文件中的原始行顺序已更改**。
接下来,让我们看看另外两种解决问题的方法,无需对输入文件进行排序。
5. 使用grep命令
grep命令擅长搜索和匹配模式。如果我们将delete_list.txt中的行视为模式,我们的问题可以转化为:“在 books.txt 文件中查找与 delete_list.txt文件中的模式不匹配的行。”
因此,我们可以使用 grep命令解决它。我们先看一下解决方案,了解我们后面在命令中使用的选项:
$ grep -Fvxf delete_list.txt books.txt
PRIDE AND PREJUDICE - Austen,Jane
AROUND THE WORLD IN 80 DAYS - Verne, Jules
LES MISERABLES - Hugo,Victor
ANNA KARENINA - Tolstoy, Leo
CRIME AND PUNISHMENT - Dostoevsky, Fyodor
JANE EYRE - Bronte, Charlotte
上面的输出显示保存在 delete_list.txt文件中的行已被删除。此外,还保留了books.txt文件中的行顺序。
我们在上面的grep命令中使用了四个选项:
- -f delete_list.txt – grep将从文件 delete_list.txt中获取模式
- -x - 我们告诉grep仅在整行匹配时才考虑匹配
- -v - 在这里,我们进行反向匹配 ,因为我们只想要那些不匹配的行
- -F - 我们将进行固定字符串匹配 而不是正则表达式匹配
6. 使用awk 命令
awk命令是一个强大的 命令行文本处理实用程序。它可以处理多个输入文件 。
因此,我们可以使用简单的 awk one-liner 来解决问题:
$ awk 'NR==FNR{del[$0];next} !($0 in del)' delete_list.txt books.txt
PRIDE AND PREJUDICE - Austen,Jane
AROUND THE WORLD IN 80 DAYS - Verne, Jules
LES MISERABLES - Hugo,Victor
ANNA KARENINA - Tolstoy, Leo
CRIME AND PUNISHMENT - Dostoevsky, Fyodor
JANE EYRE - Bronte, Charlotte
最后,让我们了解一下短命令是如何工作的:
- NR==FNR{del[$0];next} – 当我们处理 *delete_list.txt 时,*我们将每一行保存在一个关联数组 del
- !($0 in del) – 当我们读取第二个文件books.txt时,我们匹配并打印不在数组del中的行