【问题标题】:How to check for multiple words inside a folder如何检查文件夹中的多个单词
【发布时间】:2017-10-01 21:08:37
【问题描述】:

我在一个名为words.txt 的文本文件中有一个词,我需要检查这些词是否在我的源文件夹中,该文件夹还包含子文件夹和文件。

我能够使用以下代码将所有单词放入一个数组中:

array_of_words = [] 

File.readlines('words.txt').map do |word|
  array_of_words << word
end

我也(有点)想出了如何使用以下方法搜索整个源文件夹,包括子文件夹和子文件:

Dir['Source/**/*'].select{|f| File.file?(f) }.each do |filepath|
  puts filepath
  puts File.readlines(filepath).any?{ |l| l['api'] } 
end

我不想搜索像api 这样的单词,而是想在源文件夹中搜索整个单词数组(如果可能的话)。

【问题讨论】:

  • 您必须在 ruby​​ 中执行此操作吗?命令行工具egrep 可以通过类似egrep -r "(api|function|method)" *...
  • 嘿@Brian,是的,不幸的是它必须在 ruby​​ 中。

标签: ruby search directory subdirectory


【解决方案1】:

考虑一下:

File.readlines('words.txt').map do |word|
  array_of_words << word
end

会将整个文件读入内存,然后将其转换为数组中的单个元素。您可以使用以下方法完成同样的事情:

array_of_words = File.readlines('words.txt')

一个潜在的问题是它不可扩展。如果“words.txt”大于可用内存,您的代码就会出现问题,所以要小心。

可以通过多种方式在文件中搜索单词数组,但我一直发现使用正则表达式最容易。 Perl 有一个很棒的模块,叫做 Regexp::Assemble,它可以很容易地将单词列表转换为非常有效的模式,但是 Ruby 缺少这种功能。请参阅“Is there an efficient way to perform hundreds of text substitutions in Ruby?”了解我过去整理的一种解决方案。

Ruby 确实有 Regexp.union,但这只是部分帮助。

words = %w(foo bar)
re = Regexp.union(words) # => /foo|bar/

生成的模式具有表达式的标志,因此您必须小心将其插入另一个模式:

/#{re}/ # => /(?-mix:foo|bar)/

(?-mix: 会给你带来麻烦,所以不要那样做。而是使用:

/#{re.source}/ # => /foo|bar/

这将生成模式并按照我们预期的方式运行。

不幸的是,这也不是一个完整的解决方案,因为换句话说,这些单词可以作为子字符串找到:

'foolish'[/#{re.source}/] # => "foo"

解决这个问题的方法是围绕模式设置单词边界:

/\b(?:#{re.source})\b/ # => /\b(?:foo|bar)\b/

然后查找整个单词:

'foolish'[/\b(?:#{re.source})\b/] # => nil

更多信息请参见 Ruby 的 Regexp 文档。

一旦有了想要使用的模式,搜索就变得更简单了。 Ruby 有Find 类,它可以很容易地递归搜索目录中的文件。文档介绍了如何使用它。

或者,您可以使用Dir 类来拼凑您自己的方法。同样,它在文档中有使用它的示例,但我通常使用 Find。

读取您正在扫描的文件时,我建议使用foreach 逐行读取文件。 File.readFile.readlines不可可扩展的,当 Ruby 试图将一个大文件读入内存时,它们会使你的程序行为异常。相反,foreach 将产生运行速度更快的可扩展代码。有关详细信息,请参阅“Why is "slurping" a file not a good practice?”。

使用上面的链接,您应该能够快速将一些东西组合在一起,这些东西可以高效地运行并且很灵活。


这个未经测试的代码应该可以帮助您入门:

WORD_ARRAY = File.readlines('words.txt').map(&:chomp)
WORD_RE = /\b(?:#{Regexp.union(WORD_ARRAY).source}\b)/

Dir['Source/**/*'].select{|f| File.file?(f) }.each do |filepath|
  puts "#{filepath}: #{!!File.read(filepath)[WORD_RE]}"
end

它会输出它正在读取的文件,以及“真”或“假”是否在列表中找到一个单词。

由于readlinesread,它不可扩展,如果任何文件很大,它可能会严重减速。同样,请参阅上面“slurp”链接中的注意事项。

【讨论】:

  • 您好,感谢您提供这个惊人/有用的信息,我会想出一个更好的解决方案,但是这个定义。有帮助!
【解决方案2】:

递归搜索目录中包含在words.txt中的任何单词

re = /#{File.readlines('words.txt').map { |word| Regexp.quote(word.strip) }.join('|')}/

Dir['Source/**/*.{cpp,txt,html}'].select{|f| File.file?(f) }.each do |filepath|
  puts filepath
  puts File.readlines(filepath, "r:ascii").grep(re).any?
end

【讨论】:

  • 我更新了答案以逃避 words.txt 的内容
  • 嘿,所以我得到了同样的错误。 `===': UTF-8 中的无效字节序列 (ArgumentError)
  • Regexp.quote(word.strip) }.join('|') 不是一个好主意,因为它会生成误报的子字符串命中。
  • @Hamel Desai - 我更新了答案以尽量避免搜索二进制文件
  • 错误消息表明您的文件(在 source 或 words.txt 中)中包含非 UTF-8 字符。因此,也许尝试以 UTF-8 以外的方式打开文件。所以,我更新了我的答案,将文件打开为ascii 也许这会有所帮助。再说一次,也许是非 UTF-8 的 words.txt。如果您知道文件的编码,可以改用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-01
  • 2011-05-06
相关资源
最近更新 更多