Получить номер строки из preg_match_all()

Я использую PHP preg_match_all() для поиска строки, импортированной с помощью file_get_contents(). Регулярное выражение возвращает совпадения, но я хотел бы знать, по какому номеру строки эти совпадения найдены. Какая лучшая техника для этого?

Я мог бы прочитать файл как массив и выполнить регулярное выражение для каждой строки, но проблема в том, что мое регулярное выражение соответствует результатам возврата каретки (новые строки).

Ответ 1

Хорошо, это немного поздно, может быть, вы решили это сделать, но я должен был сделать это, и это довольно просто. используя флаг 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;

вуаля!

Ответ 2

Вы не можете делать это только с регулярными выражениями. По крайней мере, не чисто. Что вы можете сделать, чтобы использовать флаг PREG_OFFSET_CAPTURE для preg_match_all и провести парсинг сообщений всего файла.

Я имею в виду, что после того, как у вас есть массив совпадающих строк и начальные смещения для каждой строки, просто подсчитайте, сколько \r\n или \n или \r находятся между началом файла и смещением для каждого совпадения. Номер строки совпадения будет состоять из числа отдельных терминаторов EOL (\r\n | \n | \r) плюс 1.

Ответ 3

У вас есть пара вариантов, но ни один из них не является "простым":

a) exec() и используйте команду system grep, которая может сообщать номера строк:

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

b) Slurp в файле с помощью 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-инъекции. С положительной стороны, вам не нужно прерывать весь файл и немного сэкономить на издержках памяти.

b) Вы защищены от атак оболочки, но вам нужно выполнить разбор во всем файле. Если ваш файл большой, вы, вероятно, исчерпаете доступную память.

c) Вы вызываете регулярное выражение каждой строки, что будет иметь значительные накладные расходы, если вы имеете дело с большим количеством строк.

Ответ 4

Я думаю, прежде всего, вам нужно прочитать $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
}
}

Ответ 5

$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

(проверьте свои требования к производительности)

Ответ 6

Вы можете использовать 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++;
}

Ответ 7

Это работает, но выполняет новую 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;
                    }
                }
            }
        }
    }
}}

Ответ 8

//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;