【问题标题】:Searching multiple files for list of words in a text file在多个文件中搜索文本文件中的单词列表
【发布时间】:2018-04-08 03:22:29
【问题描述】:

我需要浏览大量文本文件并列出包含另一个文本文件中列出的所有单词的文件。

我只需要列出包含所有单词的文件。它不必是任何特定的顺序。我尝试使用各种 grep 命令,但它只输出包含任何单词的文件,而不是全部。最好使用包含单词列表的 txt 文件来搜索 grep。

  • 预期的输出是搜索成功的文件列表(包含“查询”文本文件中所有单词的文件)

试过了

grep -Ffw word_list.txt /*.fas

find . -exec grep "word_list.txt" '{}' \; -print

我找到了使用许多管道的解决方案,例如

awk "/word1/&&/word2/&&/word3/" ./*.txt

find . -path '*.txt' -prune -o -type f -exec gawk '/word1/{a=1}/word2/{b=1}/word3/{c=1}END{ if (a && b && c) print FILENAME }' {} \;

但我有一个庞大的单词列表,不切实际。

谢谢。

【问题讨论】:

  • 你能贴出你试过的代码/命令吗?
  • 如果您展示到目前为止您尝试过的代码并描述您遇到的问题,您将在这里得到更友好的接待和更好的帮助。如果没有代码,您的问题看起来像是请求免费咨询,很多人不喜欢这样。
  • 感谢您的提示,抱歉含糊不清,我编辑了问题,添加了一些我尝试使用的代码。
  • 如果您需要定期执行此操作,则需要考虑替代解决方案。要么是 gnu 并行搜索,要么是搜索引擎系统,所有这些都需要额外的磁盘、网络和 CPU。祝你好运。
  • 另外,++ 用于提高您的 Q,但更进一步,显示您搜索所需的输出。您是否希望您的搜索仅列出成功的文件名?祝你好运。

标签: linux grep


【解决方案1】:

给定示例文件

file1.txt
word1
word2
word4
word5
file2.txt
word1
word2
word3
word4
file3.txt
word2
word3
word4
file4.txt
word0
word1
word2
word3
word4
file5.txt
word0
word1
word2
word3
word4
word5

这个老式的 awk/shell 代码

#!/bin/bash

wordList="$1"
shift
awk -v wdListFile="$wordList" '
  BEGIN{
    dbg=0
    while(getline < wdListFile > 0 ) {
      words[$0]=$0
      flags[$0]=0
      numFlags++
    }
  }
  {
    if (dbg) { print "#dbg: myFile=" myFile " FILENAME=" FILENAME }
    if (myFile != FILENAME) {
      # a minor cost of extra reset on the first itteration in the run
      if (dbg) { print "#dbg: inside flags reset" }
      for (flg in flags) {
          flags[flg]=0
      }
    }

    for (i=1; i<=NF; i++) {
      if (dbg) { print "#dbg: $i="$i }
      if ($i in words) {
        flags[$i]++
      }
    }
    matchedCnt=0
    for (f in flags) {
      if (dbg) { print "#dbg: flags["f"]="flags[f] }
      if (flags[f] > 0 ) {
          matchedCnt++
          if (dbg) { print "#dbg: incremeted matchedCnt to " matchedCnt}
      }
    }
    if (dbg) {print "#dbg: Testing matchedCnt=" matchedCnt "==numFlags=" numFlags}
    if (matchedCnt == numFlags) {

      if (dbg) { print "All words found in "FILENAME "matchedCnt=" matchedCnt "  numFlags=" numFlags}
      print FILENAME
      nextfile
    }
    myFile=FILENAME
    if (dbg) { print "#dbg: myFile NOW=" myFile }
  }' $@

从命令行运行

./genGrep.sh wd.lst file*.txt 

产生以下输出

file2.txt
file4.txt
file5.txt

只有一次,使脚本可执行

chmod 755 ./genGrep.sh

我建议使用名称中的dbg 制作此文件的副本,然后获取原始副本并删除所有带有dbg 的行。这样,如果您需要,您将拥有一个 dbg 版本,但 dbg 行增加了大约 20% 的代码阅读量。

请注意,您可以通过设置dbg=1 来打开所有dbging,或者您可以通过添加! 字符来打开各个行,即if (! dbg) { ...}

如果由于某种原因您在非常旧的 Unix 硬件上运行,nextfile 命令可能不起作用。查看您的系统是否有可用的gawk,或者安装它。 如果不是内置的,我认为获取 nextfile 行为有一个技巧,但我现在不想花时间研究它。

请注意,使用flags[] 数组、matchedCnt 变量和内置awk 函数nextfile 旨在在找到所有单词后停止在文件中搜索。

您还可以添加一个参数来表示“如果 n 百分比匹配,则打印文件名”,但这会附带咨询费率。

如果您不理解精简后的 awk 代码(删除 dbg 部分),请在提问之前按照自己的方式联系 Grymoire's Awk Tutorial

管理数千个文件(如您所说)是一个单独的问题。但是为了让事情顺利进行,我会打电话给genGrep.sh wd.lst A* ; genGrep.sh wd.lst B*; ... 并希望它能奏效。问题是命令行有一个可以在文件名列表中一次处理的字符的限制。因此,如果A* 扩展到 10 亿个字符,那么您必须找到一种方法将行大小分解为 shell 可以处理的内容。

通常,这是通过xargs 解决的,所以

find /path/to/files -name 'file*.txt' | xargs -I {} ./genGrep.sh wd.lst {}

将找到您通过通配符指定的所有文件,如您作为find 的第一个参数列出的一个或多个/path/to/file。 所有匹配的文件都通过管道发送到xargs,它从列表中读取一个命令调用可以处理的所有文件,并继续循环(对您不可见),直到所有文件都已处理完毕。

如果您的计算机上有额外的“核心”可用,xargs 有额外的选项允许运行./genGrep.sh 的多个副本。我不想深入探讨,因为我不知道其余部分是否真的适用于您的实际使用。

IHTH

【讨论】:

  • 这似乎是迄今为止最有效的解决方案。它还可以正确处理超过最大命令行长度 (128kB-8MB) 的列表。
  • xargs 的使用是不安全的:文件file 3"x2".txt 会搞砸。将xargs 替换为来自GNU Parallel 的parallel,它将起作用。
  • @OleTange:感谢 cmets。我同意xargs 可能是不安全的,但我认为解决主要问题是我可以抽出时间。如果 O.P. 在 Q 上发布关注,我将尝试帮助解决任何问题。如果没有反馈(或更好定义的 Q),则不清楚需要处理哪种文件名。文件名中的空格会使其爆炸,并且文件名中的\n 字符(可能发生)也不会很好;-) .. 如果我们使用null 终止,xargs 会遇到file 3"x2".txt 的问题文件名,即 find /path -name '*.txt' -print0 | xargs -0 ... ?祝大家好运!
【解决方案2】:

这是一个小技巧,因为在 grep 中没有直接的 AND 方法。我们可以使用 grep -E 选项来模拟 AND。

grep -H -E "word1" *.txt| grep -H -E "word2" *.txt|grep -H -E "word3" *.txt | grep -H -E "word4" *.txt| cut -d: -f1 

-H =>  --with-filename
-E => --extended-regexp
cut -d: -f1 => to print only the file name.

【讨论】:

  • 嗨@SAB!谢谢你的回答。我对我的文件进行了一些测试,它输出了包含搜索中任何单词的文件的名称,而不是全部。有什么想法吗?
【解决方案3】:

尝试类似:

WORD_LIST=file_with_words.txt
FILES_LIST=file_with_files_to_search.txt
RESULT=file_with_files_containing_all_words.txt

# Generate a list of files to search and store as provisional result
# You can use find, ls, or any other way you find useful
find . > ${RESULT}

# Now perform the search for every word
for WORD in $(<${WORD_LIST}); do
    # Remove any previous file list
    rm -f ${FILES_LIST}
    # Set the provisional result as the new starting point
    mv ${RESULT} ${FILES_LIST}
    # Do a grep on this file list and keep only the files that
    # contain this particular word (and all the previous ones)
    cat ${FILES_LIST} | xargs grep -l > $RESULT
done

# Clean up temporary files
rm -f ${FILES_LIST}

此时,您应该在 $RESULTS 中拥有包含 ${WORD_LIST} 中所有单词的文件列表。

此操作代价高昂,因为您必须为检查的每个单词一次又一次地读取所有(静止的)候选文件,因此请尝试将频率较低的单词放在 ${WORD_LIST} 的第一位,这样您就可以尽快从检查中删除尽可能多的文件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多