【问题标题】:How to get matches from a text file using Php with a specified start and end point?如何使用具有指定起点和终点的 Php 从文本文件中获取匹配项?
【发布时间】:2022-01-11 18:47:37
【问题描述】:

我有一个 PHP 脚本,它使用 preg_match_all 函数从文本文件中返回所有匹配项。但是,我希望该函数仅在每行中检查从位置 3 开始且长度为 11 位(基本上,结束位置为 13)的匹配,而不是在整行中查找匹配,因为这将返回错误结果.

脚本:

<?php
$file = 'masterfile.out';
$searchfor = '02354098780';

// the following line prevents the browser from parsing this as HTML.
header('Content-Type: text/plain');

// get the file contents, assuming the file to be readable (and exist)
$contents = file_get_contents($file);
// escape special characters in the query
$pattern = preg_quote($searchfor, '/');
// finalise the regular expression, matching the whole line
$pattern = "/^.*$pattern.*\$/m";

// search, and store all matching occurrences in $matches
if(preg_match_all($pattern, $contents, $matches)){
   echo "Found matches:\n";
   echo substr(implode("\n", $matches[0]),2,11);
   echo substr(implode("\n", $matches[0]),166,11);
}
else{
   echo "No matches found";
}
?> 

文本文件示例数据:

I0023540987805R01  ABC                         GHI                       OLirrt                 000000000000000100EA 0812160070451700   1098833   1990041300000001086000000000108600000000000996000000000032100000000000000000000000000000000000000000000000000000000000000000000006589000000000000000                                     P0012B    
 0000002032902R01  DEF                         JKL                       KLijuI                 000000000000000100EA 0812160070451700   1029132   1997010800000002396000000000239600000120002326000000000000000000000000000000000000000000000000000000000000004560000000000000000000000000987600000000                                     A203SD   

【问题讨论】:

  • 你能把文本文件的内容贴出来吗?
  • @tcj 当然,我将其添加到帖子中。
  • 我的文本文件例子02354098780是否存在?它应该出现在哪里?在任何行起始位置 3 是吗?另外,示例中的行在哪里结束?on : P0012B?
  • @Shlomtzion 是的,值:02354098780 可以在从位置 3 开始的第 1 行找到。第 1 行在 P0012B 结束。在给定的示例中,第 2 行从 0000002032902R01 开始。

标签: php regex


【解决方案1】:

对于少量字符,可以正则表达式到行首:

'#^..([0-9]{13})#'

将搜索 13 位数字,忽略行首 (^) 的前两个字符 (.),包括第三个字符。

在这种情况下:

<?php
$file      = 'masterfile.out';
// $pattern   = '#^..([0-9]{11})#m'; // Any 11 digits
$pattern   = '#^..(02354098780)#m';   // Exactly these 11

// the following line prevents the browser from parsing this
// as HTML.
header('Content-Type: text/plain');

// get the file contents, assuming the file to be readable (and exists)
$contents = file_get_contents($file);

if (preg_match_all($pattern, $contents, $matches, PREG_PATTERN_ORDER)){
   echo "Found matches:\n";
   echo implode("\n", $matches[1]);
   echo "\n";
} else {
   echo "No matches found\n";
}

更新

我刚刚注意到您的序列开始于第三个字符从 1 开始。在某些标准中(在我的早期示例中),您从 0 开始计数。因此,如果您从 1 开始,则只需要 两个 点,而不是三个点。换句话说,当您说“从位置 3 开始”时,您可能的意思是跳过 前两个 字符,而 - 正如您从其他答案中看到的那样 - 几乎每个人都认为您想跳过 三个个字符。

【讨论】:

  • 我把这个放在哪里?
  • 添加(修改)示例代码来回答。我包括了 11 位正则表达式,因为您说的是 11 位,但您的代码仅搜索 2354098780。
【解决方案2】:

如果您的示例接近您的预期用途,那么您实际上是在搜索子字符串的完全匹配,但使用的是 preg_match_all。但是,遍历行应该具有较低的内存影响,并且对于精确相等的严格子字符串比较具有比 preg_match_all 更低的 cpu 影响。

所以我建议这样做。这可以使用fgetsstream_get_line 来实现,这可能会稍微提高性能(尽管在大多数情况下这无关紧要)。

这可以通过以下方式实现:

$searchString = 'someFixedString';
$posOffset = 2;
$matchLength = mb_strlen($searchString);
$filePath = '/some/file.path';
$fileHandle = @fopen($filePath, 'r+');
$checkedLines = 0;
$matches = [];
$foundMatches = false;

//Depending on what you wish to output
$capturePosOffset = 0;
$captureLength = $matchLength + $posOffset + 3;

// if lines are  no longer than 8192 bytes,
// otherwise set to a value above the byte-length of your lines
$maxBytesToReadPerLine = 0; 

// if file line-terminator is as in PHP, 
// otherwise set to file's line-terminator
$lineTerminator = PHP_EOL;

if ($fileHandle) {
    while (!feof($fileHandle)) {
       $checkedLines++;
       // or just use fgets, which requires no further arguments
       $line = stream_get_line($fileHandle, $maxBytesToReadPerLine, $lineTerminator);
       if (mb_substr($line, $posOffset, $matchLength) === $searchString) { 
           $foundMatches = true;
           $matches[] = $line;
           // Or, if you want to capture a field with a fixed Length
           // (modify the offset and length arguments above)
           $matches[] = mb_substr($line, $capturePosOffset, $captureLength);
       }
    }
}
if ($foundMatches) {
    echo "Found " . count($matches) . " matches among $checkedLines lines:" . PHP_EOL;
    foreach ($matches as $matchedValue) {
        // I'm not sure what you intend to do here.
        // - In your example code, it appears you
        // implode the array, but then only output
        // 11 characters of the first line starting at position 3.
        // If you want the whole line, you can capture it above
        // and echo it here.

        // Or if you want, you can capture and output the first field
        // by modifying $capturePosOffset and $captureLength
        // by merely echoing the value (and a newline)
        echo '  ' . $matchedValue . PHP_EOL;
    }
} else {
    echo "No matches found!" . PHP_EOL;
}

我们使用 mb_strlenmb_substr 以防编码允许多字节字符 - 只有当您知道绝对不是这种情况时才能安全使用 strlensubstr

人们不应该陷入过早的优化中,但请注意:哪种解决方案最优化将在很大程度上取决于文件大小和匹配长度。

【讨论】:

  • 谢谢。是的,我已经更新了帖子。我需要回显从位置 3 开始的 11 位数字,然后从匹配行的位置 166 开始的 11 位数字。
  • 我明白了。在这种情况下,您可以在我的示例中修改$matches[] = ... 代码。尽管您似乎仍在为每个匹配项捕获和输出固定的搜索字符串。由于您已经知道要搜索的内容,因此您可能根本不需要输出它,或者您可能只在开始时输出一次搜索字符串 - 在这种情况下,您只需要 $matches[] = mb_substr($line, 166, 11);
  • 我试过了,但我收到了这个错误:致命错误:未捕获的 ArgumentCountError:stream_get_line() 需要至少 2 个参数
  • 哦,是的,对不起 - 我的错误。 stream_get_line 需要 max-byte-length-to-read 作为第二个参数,并具有可选的第三个行终止符参数。您可以使用fgets,它除了文件句柄之外不需要任何参数,或者在我修改后的示例中使用stream_get_line - 第二个参数至少与行的最大字节长度和文件的行一样长 -终止符作为第三个参数。
  • 为了调试,您可能希望在 while 循环体的开头增加一个计数器,然后稍后输出 - 以检查您是否单独读取所有行(我会很快适应我的例子)。如果你正在阅读所有行,你应该检查你得到了多少匹配 - 例如使用echo 'Found ' . count($matches) . ' matching lines: ' . PHP_EOL; 而不是仅仅回显“找到匹配项:” - (如果您在每个找到的行中只添加一个条目。如果您在每个匹配项中添加两个条目,那么您应该先除以二)。
【解决方案3】:

以下正则表达式忽略每行开头的前 3 个字符,并捕获后面的 11

https://regex101.com/r/MEaB67/1

/^.{3}(.{11})/gm

编辑

这里是一些用于测试正则表达式的示例 PHP 代码

<pre>
<?php
$pattern = '/^.{3}(.{11})/m';
$subject = '
I0023540987805R01  ABC                         GHI                       OLirrt                 000000000000000100EA 0812160070451700   1098833   1990041300000001086000000000108600000000000996000000000032100000000000000000000000000000000000000000000000000000000000000000000006589000000000000000                                     P0012B    
 0000002032902R01  DEF                         JKL                       KLijuI                 000000000000000100EA 0812160070451700   1029132   1997010800000002396000000000239600000120002326000000000000000000000000000000000000000000000000000000000000004560000000000000000000000000987600000000                                     A203SD   
';
$matches = null;
preg_match_all($pattern, $subject, $matches);
var_dump($matches);
?>
</pre>

法比奥

【讨论】:

  • 我在哪里添加这个?
  • @JackYuan,您可以将其分配给$pattern 变量——请稍等,我会在我的答案中添加一些示例 PHP 代码
  • @JackYuan,给你
  • 谢谢@Fabio,但它返回的所有值不是分配给搜索变量的匹配值。
  • @JackYuan,我明白了:我以为您会先获取匹配项,然后在数组中搜索您的值。我更喜欢这种方法,因为正则表达式更通用
【解决方案4】:

这里的方法与你的有点不同 - 因为我们正在该行的特定部分寻找一个字符串,我们可以删除其余部分并检查该字符串是否出现在所述行中。

    <?php


$text = "I0023540987805R01  ABC                         GHI                       OLirrt                 000000000000000100EA 0812160070451700   1098833   1990041300000001086000000000108600000000000996000000000032100000000000000000000000000000000000000000000000000000000000000000000006589000000000000000                                     P0012B    
0000002032902R01  DEF                         JKL                       KLijuI                 000000000000000100EA 0812160070451700   1029132   1997010800000002396000000000239600000120002326000000000000000000000000000000000000000000000000000000000000004560000000000000000000000000987600000000                                     A203SD   ";

echo '<pre>';
$txt = explode("\n",$text);

echo '<pre>';
print_r($txt);

foreach($txt as $key => $line){
    $subbedString = substr($line,2,11);

    $searchfor = '02354098780';
    //echo strpos($subbedString,$searchfor); 
    if(strpos($subbedString,$searchfor) === 0){
        $matches[$key] = $searchfor;
        $matchesLine[$key] = $line; /**Save the whole line when match is found. */
        echo "Found in line : $key";
    }

    
}

echo '<pre>';
print_r($matches);

echo '<pre>';
print_r($matchesLine);

将返回:

  Array
(
    [0] => I0023540987805R01  ABC                         GHI                       OLirrt                 000000000000000100EA 0812160070451700   1098833   1990041300000001086000000000108600000000000996000000000032100000000000000000000000000000000000000000000000000000000000000000000006589000000000000000                                     P0012B    
    [1] => 0000002032902R01  DEF                         JKL                       KLijuI                 000000000000000100EA 0812160070451700   1029132   1997010800000002396000000000239600000120002326000000000000000000000000000000000000000000000000000000000000004560000000000000000000000000987600000000                                     A203SD   
)
Found in line : 0
Array
(
    [0] => 02354098780
)
Array
(
    [0] => I0023540987805R01  ABC                         GHI                       OLirrt                 000000000000000100EA 0812160070451700   1098833   1990041300000001086000000000108600000000000996000000000032100000000000000000000000000000000000000000000000000000000000000000000006589000000000000000                                     P0012B    
)

【讨论】:

  • 完美!但是,当有匹配时,我如何返回整行。谢谢。
  • 我添加了:$matchesLine[$key] = $line;在循环中,当 if 为真时,这将创建并使用 $key - 行号和 $line 作为行本身填充数组。
  • 或者只是 echo $line - 在循环里面,你会看到它...
【解决方案5】:

你可以匹配3个字符,然后使用\K忘记目前匹配的内容,然后匹配11个数字。

^...\K\d{11}
  • ^ 字符串开始
  • ... 匹配除换行符以外的任何字符 3 次
  • \K清除当前匹配缓冲区
  • \d{11} 匹配 11 位数字

您可以省略使用preg_quote,因为在当前模式中没有什么可以转义的。

由于该模式使用锚点 ^,您必须指定多行标志 /m 才能获得所有结果。

$file = 'masterfile.out';
$contents = file_get_contents($file);
$pattern = "/^...\K\d{11}/m";

if (preg_match_all($pattern, $contents, $matches)) {
    echo "Found matches:" . PHP_EOL;
    foreach ($matches[0] as $m) {
        echo $m . PHP_EOL;
    }
} else {
    echo "No matches found";
}

输出

Found matches:
23540987805
00002032902

【讨论】:

    猜你喜欢
    • 2011-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多