2

我想在 PHP 中搜索文本文件目录并列出所有出现字符串的实例。

在 Linux 中我会使用这个:

egrep Cheese textfile_*.txt

PHP中是否有一个函数可以执行此操作而无需先将所有内容附加到数组的复杂性?

4

3 回答 3

6

一般情况

假设您想要在任意数量的任意大小的文件中匹配任意长度的字符串的解决方案,并且您的系统资源是有限的。这是最有可能的情况,也是最棘手的情况。

您不能简单地将所有文件加载到内存中并将它们作为每个文件的一个大字符串进行搜索,因为这非常消耗内存并且实际上在功能复杂性方面并不是特别有效(您循环所有文件,将它们加载到内存中,然后循环它们再次搜索内容)。

事实上,最好完全避免将整个文件加载到内存中——如果其中一个文件是 10GB 怎么办?

因此,首先,很明显,我们需要获取目录中文件的列表。有几种方法 - 我看到glob()提到过几次 - 但我会说这个算法的最佳方法是顺序读取条目并一次处理它们,而不是将整个列表加载到数组中然后对其进行迭代,这在 PHP 中意味着您要么想要opendir()函数系列,要么想要DirectoryIterator迭代器类系列之一。许多人会争辩说,在现代 PHP 中,后者是“正确”的方式。

现在您可以访问目录中的文件列表,您需要访问内容,并且您需要在不将整个文件加载到内存的情况下执行此操作。在 PHP 中,这意味着您将需要fopen()and (因为这是一个文本文件)fgets()。这允许我们一次处理一行文件,因此我们永远不会一次将超过一行的数据加载到内存中。它还有一个参数,允许您指定最大行行长度,如果文本文件出于某种原因包含很少/没有换行符,则可能应该使用它。

所以我们正在分块处理文件,我们可以只strpos()为搜索字符串的每个块,对吗?嗯,差不多。当搜索字符串跨越两个(或更多)块的边界时会发生什么?这是它开始变得有趣的地方,也是值得研究更复杂的字符串搜索算法的地方(Boyer-Moore算法的变体在这里可能会很好地为您服务)。

现在你唯一需要确定的是你希望匹配的具体程度——你想要区分大小写吗?你想原谅空白的差异吗?你想规范化字符集吗?这些是在实现字符串搜索算法之前必须回答和考虑的问题。

实际情况是,其中一些复杂性在 PHP 中解决起来相对较慢 - 如果您最终一次迭代一个字符串一个字符,例如,在 C 中可能非常快的东西,它将是 PHP 中真正的性能杀手。

你的案例

...可能不需要这种复杂性。如果您知道您将始终处理少量相当小的文件,那么简单地执行strpos(file_get_contents())组合方法很可能会很好 - 尽管无论您做什么,一次处理一个文件而不是在执行任何操作之前将它们全部加载到内存中可能需要搜索操作。


从本质上讲,您如何实现这一点取决于您正在使用的环境的一些因素 - 但这样的事情在资源消耗方面可能非常危险,您必须考虑您的代码现在和将来需要如何工作您可以构建正确的解决方案。

于 2013-09-06T11:55:57.620 回答
2

PHP中是否有一个函数可以执行此操作而无需先将所有内容附加到数组的复杂性?

不是一个特定的功能,但 PHP 有:

  • 递归目录迭代器
  • 递归迭代器迭代器
  • 递归正则表达式迭代器
  • 全局迭代器
  • 正则表达式迭代器
  • SplFileInfo
  • SplFileObject

由于这些都是迭代器,当正确堆叠在一起时,确实允许您迭代所有匹配的行。

对于您的特定情况,可能值得创建一个新的迭代器,它在其构造函数中采用类似于 egrep 的参数,并在其构造函数中进行堆叠/处理以提供简单的接口。

或者可能一个用于路径/文件/SplFileInfo 列表作为迭代器,另一个用于对这些行进行 grepping 以使其更加流畅。

您还可以在Iterator-Garden中添加一些泛型,例如将每个 SplFileInfo 转换为 SplFileObject 的 DecoratingIterator 的 ExpandingIterator。

于 2013-09-06T12:17:46.173 回答
0

使用glob函数:

$text_files = glob('directory/*.txt');

foreach($text_files as $text_file) {
   if (strpos('Cheese', file_get_contents($text_file)) !== false) {
      echo 'File ', $text_file, ' contains "Cheese"<br>';
   }
}
于 2013-09-06T11:25:37.377 回答