从preg_match_all()获取行号

时间:2011-01-19 01:24:05

标签: php regex

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

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

10 个答案:

答案 0 :(得分:12)

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

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

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

瞧!

答案 1 :(得分:11)

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

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

答案 2 :(得分:1)

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

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

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

b)使用file_get_contents()在文件中进行Slurp,将其拆分为一个行数组,然后使用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)你正在调用每一行的正则表达式,如果你处理大量的行,这会产生很大的开销。

答案 3 :(得分:1)

$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 :(得分:1)

必须使用preg_match_all和PREG_OFFSET_CAPTURE标志来解决此问题,代码注释应说明返回的数组类型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 :(得分:1)

晚于游戏,但今天我需要此功能,我意识到可以将@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 :(得分:0)

我认为首先,您需要将$ 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
}
}

答案 7 :(得分:-1)

您可以使用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++;
}

答案 8 :(得分:-1)

这样可行但在每一行上执行一个新的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;
                    }
                }
            }
        }
    }
}}

答案 9 :(得分:-1)

SELECT
  distinct c1,
  SUBSTRING( c1, REGEXP_INSTR ( c1, '[123]+' ) , 1) as First_123 , 
  REGEXP_INSTR (  SUBSTRING( c1, 1, REGEXP_INSTR ( c1, '[123]+' )) , '[s]') as NumberOfS
FROM table