【问题标题】:Get line number from preg_match_all()从 preg_match_all() 获取行号
【发布时间】:2011-06-11 11:30:30
【问题描述】:

我正在使用 PHP 的 preg_match_all() 来搜索使用 file_get_contents() 导入的字符串。正则表达式返回匹配项,但我想知道在哪个行号找到这些匹配项。实现这一目标的最佳技术是什么?

我可以将文件作为数组读取并为每一行执行正则表达式,但问题是我的正则表达式匹配回车符(新行)的结果。

【问题讨论】:

  • 我猜测一下,您可能无法为此使用preg_match_all
  • preg_split 并计算结果中的行数?现在我这么说听起来很愚蠢。
  • 我没有看到任何简单的方法来完成你想做的事情......

标签: php regex


【解决方案1】:

好吧,有点晚了,也许你已经解决了这个问题,但我必须这样做,而且相当简单。 在preg_match 中使用PREG_OFFSET_CAPTURE 标志将返回匹配的字符位置。 让我们假设 $charpos,所以

list($before) = str_split($content, $charpos); // fetches all the text before the match

$line_number = strlen($before) - strlen(str_replace("\n", "", $before)) + 1;

瞧!

【讨论】:

    【解决方案2】:

    你不能只用正则表达式来做到这一点。至少不干净。你能做什么来使用 preg_match_all 的PREG_OFFSET_CAPTURE 标志并对整个文件进行后解析。

    我的意思是,在您拥有匹配字符串数组和每个字符串的起始偏移量之后,只需计算文件开头和每个匹配项的偏移量之间有多少 \r\n\n\r。匹配的行号将是不同 EOL 终止符的数量 (\r\n | \n | \r) 加上 1

    【讨论】:

      【解决方案3】:
      $data = "Abba
      Beegees
      Beatles";
      
      preg_match_all('/Abba|Beegees|Beatles/', $data, $matches, PREG_OFFSET_CAPTURE);
      foreach (current($matches) as $match) {
          $matchValue = $match[0];
          $lineNumber = substr_count(mb_substr($data, 0, $match[1]), PHP_EOL) + 1;
      
          echo "`{$matchValue}` at line {$lineNumber}\n";
      }
      

      输出

      `Abba` at line 1
      `Beegees` at line 2
      `Beatles` at line 3
      

      (检查您的性能要求)

      【讨论】:

        【解决方案4】:

        使用带有 PREG_OFFSET_CAPTURE 标志的 preg_match_all 是解决此问题所必需的,代码 cmets 应说明 preg_match_all 返回的数组类型以及如何计算行号:

        // Given string to do a match with
        $string = "\n\nabc\nwhatever\n\ndef";
        
        // Match "abc" and "def" in a string
        if(preg_match_all("#(abc).*(def)#si", $string, $matches, PREG_OFFSET_CAPTURE)) {
          // Now $matches[0][0][0] contains the complete matching string
          // $matches[1][0][0] contains the results for the first substring (abc)
          // $matches[2][0][0] contains the results for the second substring (def)
          // $matches[0][0][1] contains the string position of the complete matching string
          // $matches[1][0][1] contains the string position of the first substring (abc)
          // $matches[2][0][1] contains the string position of the second substring (def)
        
          // First (abc) match line number
          // Cut off the original string at the matching position, then count
          // number of line breaks (\n) for that subset of a string
          $line = substr_count(substr($string, 0, $matches[1][0][1]), "\n") + 1;
          echo $line . "\n";
        
          // Second (def) match line number
          // Cut off the original string at the matching position, then count
          // number of line breaks (\n) for that subset of a string
          $line = substr_count(substr($string, 0, $matches[2][0][1]), "\n") + 1;
          echo $line . "\n";
        }
        

        这将为第一个子字符串返回3,为第二个子字符串返回6。如果您使用不同的换行符,您可以将\n 更改为\r\n\r

        【讨论】:

          【解决方案5】:

          游戏晚了,但我今天需要这个功能,我意识到 @Javier's@iguito's 答案可以组合成一个简单的解决方案。对于我的用例,我还将\n 的检查替换为PHP_EOL

          // Get your matches
          preg_match_all( '[YOUR REGEX HERE]', $data, $matches, PREG_OFFSET_CAPTURE );
          
          // This is my loop format, yours may need to be different
          foreach ( $matches[0] as $match ) {
          
              // Get the line number for the current match 
              list( $before ) = str_split( $data, $match[1] );
              $line_number = substr_count( $before, PHP_EOL ) + 1;
              echo $line_number;
          
          }
          

          【讨论】:

            【解决方案6】:

            你有几个选择,但没有一个是“简单的”:

            a)exec()和使用系统grep命令,可以报行号:

            exec("grep -n 'your pattern here' file.txt", $output);`
            

            b) 使用file_get_contents() 插入文件,将其拆分为行数组,然后使用preg_grep() 查找匹配的行。

            $dat = file_get_contents('file.txt');
            $lines = explode($dat, "\n");
            $matches = preg_grep('/your pattern here/', $lines);
            

            c) 以行大小的块读取文件,保持运行的行数,并在每一行上进行模式匹配。

            $fh = fopen('file.txt', 'rb');
            $line = 1;
            while ($line = fgets($fh)) {
                 if (preg_match('/your pattern here/', $line)) {
                     ... whatever you need to do with matching lines ...
                 }
                 $line++;
            }
            

            各有优劣

            a) 您正在调用一个外部程序,并且如果您的模式包含任何用户提供的数据,那么您可能会将自己暴露在等同于 SQL 注入攻击的 shell 面前。从好的方面来说,您不必在整个文件中大吃一惊,并且会节省一些内存开销。

            b) 你可以免受 shell 注入攻击,但你必须在整个文件中啜饮。如果您的文件很大,您可能会耗尽可用内存。

            c) 您在每行都调用一个正则表达式,如果您要处理大量行,这将产生很大的开销。

            【讨论】:

            • 我想你错过了我问题的这一部分:我可以将文件作为数组读取并为每一行执行正则表达式,但问题是我的正则表达式匹配回车符的结果(新行) .
            【解决方案7】:

            我认为首先,您需要将 $String 读入一个数组,每个元素代表每一行,并且看起来像这样:

            $List=file($String);
            for($i=0;$i<count($List),$i++){
            if(preg_match_all()){;//your work here
            echo $i;//echo the line number where the preg_match_all() works
            }
            }
            

            【讨论】:

            • 我想你错过了我问题的这一部分:我可以将文件作为数组读取并为每一行执行正则表达式,但问题是我的正则表达式匹配回车符的结果(新行) .
            【解决方案8】:

            您可以使用 preg_match_all 查找每个换行符的偏移量,然后将它们与您已有的偏移量进行比较。

            // read file to buffer
            $data = file_get_contents($datafile);
            
            // find all linefeeds in buffer    
            $reg = preg_match_all("/\n/", $data, $lfall, PREG_OFFSET_CAPTURE );
            $lfs = $lfall[0];
            
            // create an array of every offset
            $linenum = 1;
            $offset = 0;    
            foreach( $lfs as $lfrow )
            {
                $lfoffset = intval( $lfrow[1] );
                for( ; $offset <= $lfoffset; $offset++ )
                    $offsets[$offset] = $linenum;   // offset => linenum
                $linenum++;
            }
            

            【讨论】:

              【解决方案9】:

              这可行,但会在每一行上执行一个新的preg_match_all,这可能会非常昂贵。

              $file = file.txt;
              
              $log = array();
              
              $line = 0;
              
              $pattern = '/\x20{2,}/';
              
              if(is_readable($file)){
              
                  $handle = fopen($file, 'rb');
              
                  if ($handle) {
              
                      while (($subject = fgets($handle)) !== false) {
              
                          $line++;
              
                          if(preg_match_all ( $pattern,  $subject, $matches)){
              
                              $log[] = array(
                                  'str' => $subject, 
                                  'file' =>  realpath($file),
                                  'line' => $line,
                                  'matches' => $matches,
                              );
                          } 
                      }
                      if (!feof($handle)) {
                          echo "Error: unexpected fgets() fail\n";
                      }
                      fclose($handle);
                  }
              }
              

              或者,您可以在获取行号后读取文件,然后对整个文件执行preg_match_all 并捕获匹配偏移量。

              $file = 'file.txt';
              $length = 0;
              $pattern = '/\x20{2,}/';
              $lines = array(0);
              
              if(is_readable($file)){
              
                  $handle = fopen($file, 'rb');
              
                  if ($handle) {
              
                      $subject = "";
              
                      while (($line = fgets($handle)) !== false) {
              
                          $subject .= $line;
                          $lines[] = strlen($subject);
                      }
                      if (!feof($handle)) {
                          echo "Error: unexpected fgets() fail\n";
                      }
                      fclose($handle);
              
                      if($subject && preg_match_all ( $pattern, $subject, $matches, PREG_OFFSET_CAPTURE)){
              
                          reset($lines);
              
                          foreach ($matches[0] as $key => $value) {
              
                              while( list($line, $length) = each($lines)){ // continues where we left off
              
                                  if($value[1] < $length){
              
                                      echo "match is on line: " . $line;
              
                                      break; //break out of while loop;
                                  }
                              }
                          }
                      }
                  }
              }}
              

              【讨论】:

                【解决方案10】:
                //Keep it simple, stupid
                
                $allcodeline = explode(PHP_EOL, $content);
                
                foreach ( $allcodeline as $line => $val ) :
                    if ( preg_match("#SOMEREGEX#i",$val,$res) ) {
                        echo $res[0] . '!' . $line . "\n";
                    }
                endforeach;
                

                【讨论】:

                • 我想你错过了我问题的这一部分:我可以将文件作为数组读取并为每一行执行正则表达式,但问题是我的正则表达式匹配回车符的结果(新行) .
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-11-22
                • 1970-01-01
                相关资源
                最近更新 更多