相当于 Linux 在 PHP 中的 egrep 用于文本文件目录


Equivalent of linux's egrep in PHP for a directory of text files?

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

在Linux中,我会使用这个:

egrep Cheese textfile_*.txt

PHP 中是否有一个函数可以模拟它,而无需首先将所有内容附加到数组中的复杂性?

一般情况

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

您不能简单地将所有文件加载到内存中并

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

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

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

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

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

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

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

您的案例

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

<小时 />

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

PHP 中是否有一个函数可以模拟它,而无需首先将所有内容附加到数组中的复杂性?

不是一个特定的函数,但PHP有:

  • 递归目录迭代器
  • 递归迭代器迭代器
  • 递归RegexIterator
  • 球体迭代器
  • RegexIterator
  • SplFileInfo
  • SplFileObject

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

对于您的具体情况,也许值得创建一个新的迭代器,该迭代器在其构造函数中采用类似于 egrep 的参数,并在其构造函数中进行堆叠/处理,以提供一个简单的界面。

或者也许一个用于路径/文件/SplFileInfo 列表作为迭代器,另一个用于在这些行上进行 greping 以使其更加流畅。

你也可以在 Iterator-Garden 中加入一些泛型,比如装饰迭代器的扩展迭代器,它将每个 SplFileInfo 转换为 SplFileObject。

使用 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>';
   }
}