给定单词列表,对文本正文进行全词匹配

Whole-word matching on a body of text, given a list of words

注:

在进入正题之前,我想指出一些其他的 SO 帖子,它们没有完全回答我的问题,并且不是这个帖子的重复:

背景:

我在名为 words.txt 的文件中有一个单词列表(每行一个单词)。我想从另一个更大的文件 file.txt 中找到所有行,该文件包含 words.txt 中的任何单词。但是,我只想要 全字 匹配。这意味着当来自 file.txt 的一行至少包含一个来自 words.txt 的词被找到 "all by itself" 的实例时,应该进行匹配(我知道这是模糊的,所以请允许我解释一下).

换句话说,应该在以下情况下进行匹配:

  1. 这个词单独在一条线上
  2. 单词被 non-alphanumeric/non-hyphen 个字符包围
  3. 单词在一行的开头,后面跟着一个non-alphanumeric/non-hyphen字符
  4. 单词在一行的末尾,前面有一个 non-alphanumeric/non-hyphen 个字符

例如,如果 words.txt 中的一个词是 cat,我希望它的行为如下:

cat              #=> match
cat cat cat      #=> match
the cat is gray  #=> match
mouse,cat,dog    #=> match
caterpillar cat  #=> match
caterpillar      #=> no match
concatenate      #=> no match
bobcat           #=> no match
catcat           #=> no match
cat100           #=> no match
cat-in-law       #=> no match

之前的研究:

有一个 grep 命令 几乎 适合我的需要。具体如下:

grep -wf words.txt file.txt

其中的选项是:

-w, --word-regexp
       Select only those lines containing matches that form whole words.
       The test is that the matching substring must either be at the beginning
       of the line, or preceded by a non-word constituent character.
       Similarly, it must be either at the end of the line or followed by a
       non-word constituent character. Word-constituent characters are
       letters, digits, and the underscore.
-f FILE, --file=FILE
       Obtain patterns from FILE, one per line. The empty file contains
       zero patterns, and therefore matches nothing.

我遇到的最大问题是它将连字符(即 -)视为 "non-word constituent character"。因此(基于上面的例子)做一个 whole-word 搜索 cat 将 return cat-in-law,这不是我想要的。

我意识到 -w 选项可能达到了很多人想要的效果。但是,在我的特殊情况下,如果一个单词(例如 cat)是连字符 followed/preceded,那么我需要将其视为更大单词的一部分(例如 cat-in-law)而不是单词本身的一个实例。

此外,我知道我可以更改 words.txt 以包含正则表达式而不是固定字符串,然后使用:

grep -Ef words.txt file.txt

哪里

-E, --extended-regexp
              Interpret PATTERN as an extended regular expression

但是,我想避免更改 words.txt 并使其不受正则表达式模式的影响。

问题:

是否有一个简单的 bash 命令可以让我给它一个单词列表并对文本正文执行 全词 匹配?

终于想到了解决办法:

grep -Ef <(awk '{print "([^a-zA-Z0-9-]|^)"[=10=]"([^a-zA-Z0-9-]|$)"}' words.txt) file.txt

解释:

  • words.txt 是我的单词列表(每行一个)。
  • file.txt 是我要搜索的正文。
  • awk命令将即时预处理words.txt,将每个单词包装在一个特殊的正则表达式中以定义其正式的开始和结束(基于我上面问题中发布的规范).
  • awk 命令被 <() 包围,因此它的输出被用作 -f 选项的输入。
  • 我正在使用 -E 选项,因为我现在输入的是正则表达式列表,而不是来自 words.txt.
  • 的固定字符串

这里的好处是 words.txt 可以保持人类可读性,并且不必包含一堆正则表达式模式。