Contents

从另一个文件 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. 使用commsort命令

我们可以使用 comm命令从两个输入文件中获取公共或唯一行。

如果我们 从books.txt中删除了delete_list.txt文件中的行,则结果与两个文件中的唯一行相同。

comm FILE1 FILE2命令的输出 包含三列:

  • 1 – FILE1独有的行
  • 2 – FILE2独有的行
  • 3 – FILE1FILE2中的行

如果我们想抑制结果中的一列,我们添加*-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. 使用joinsort命令

** join命令可以通过给定字段从两个排序的输入文件中连接行。 此外,该命令提供了*-v FILENUM*选项来打印无法连接的行。**

在我们的示例中,books.txt文件中不可连接的行正是我们正在寻找的结果。

类似地,让我们将joinsort命令组合起来,就像我们在上面的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命令仍然抱怨这个?

这是组合sortjoin命令的常见缺陷。让我们了解错误的原因。

**如果我们不定义任何键, 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中的行