Итерировать каждую строку в PHP


130

У меня есть форма, которая позволяет пользователю либо загрузить текстовый файл, либо скопировать / вставить содержимое файла в текстовое поле. Я могу легко различать эти два параметра и помещать то, что они ввели, в строковую переменную, но что мне делать дальше?

Мне нужно перебрать каждую строку строки (желательно не беспокоясь о новых строках на разных машинах), убедиться, что в ней есть ровно один токен (без пробелов, табуляции, запятых и т. Д.), Очистить данные, а затем сгенерировать SQL-запрос основанный на всех строках.

Я довольно хороший программист, поэтому я знаю общее представление о том, как это сделать, но я так давно не работал с PHP, что чувствую, что ищу неправильные вещи и, таким образом, придумываю бесполезную информацию. Ключевая проблема, с которой я столкнулся, заключается в том, что я хочу построчно читать содержимое строки. Если бы это был файл, это было бы легко.

В основном я ищу полезные функции PHP, а не алгоритм, как это сделать. Какие-либо предложения?


Вы можете сначала нормализовать символы новой строки. Этот метод s($myString)->normalizeLineEndings()доступен с github.com/delight-im/PHP-Str (библиотека под лицензией MIT), в котором есть множество других полезных помощников по строкам. Вы можете взглянуть на исходный код.
— caw

Ответы:


190

preg_split переменную, содержащую текст, и перебрать возвращенный массив:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 

Будет ли это обрабатывать ^ M в дополнение к \ n \ r?
— Topher Fangio,

Я не уверен, преобразуется ли возврат каретки ascii в \ r после его помещения в переменную. Если нет, вы всегда можете использовать вместо него split () / exlope () со значением ascii - ch (13)
— Кирилл

12
Лучшее регулярное выражение /((\r?\n)|(\r\n?))/.
— Феликс Сапарелли

3
Чтобы соответствовать Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) и редким LF + CR (\ n \ r), это должно быть:/((\r?\n)|(\n?\r))/
— Waiting for Dev ...

2
Это может привести к катастрофической бомбе для многобайтовых данных.
— pguardiario

158

Я хотел бы предложить значительно более быструю (и эффективную с точки зрения памяти) альтернативу: strtokвместо preg_split.

$separator = "\r\n";
$line = strtok($subject, $separator);

while ($line !== false) {
    # do something with $line
    $line = strtok( $separator );
}

Тестируя производительность, я 100 раз повторил тестовый файл с 17 тысячами строк: это preg_splitзаняло 27,7 секунды, тогда как strtok1,4 секунды.

Обратите внимание, что, хотя $separatorопределяется как "\r\n", strtokбудет разделяться на любой из символов, а в PHP4.1.0 пропускать пустые строки / токены.

См. Ручную запись strtok: http://php.net/strtok


21
+1 для соображений производительности при работе с большими наборами строк.
— CodeAngry

4
Хотя эта функция api представляет собой полный беспорядок (вызов с разными параметрами), это лучшее решение. Ни и prey_splitне explodeдолжны использоваться для получения структурированных фрагментов строки. Это все равно, что летать из базуки .
— Maciej Sz

1
Если вы проверите использование памяти во время работы приложения, вы увидите волшебство. Он фактически вытягивает файл, который вы читаете, в память, если вы перебираете каждую из строк, и сохраняет ваше местоположение токена. Вы захотите очистить это, чтобы действительно эффективно использовать память. php.net/strtok#103051
— АбсолютныйƵERØ

2
быстрое примечание, использование strtok()чего-то еще внутри этого whileцикла приведет к поломке. Я также использовал его, чтобы захватить все в строке до первого пробела ( stackoverflow.com/a/2477411/1767412 ), и мне потребовалась минута, чтобы понять, почему все идет не так, как планировалось
— billynoah

1
должен быть принятым ответом, вероятно, самым быстрым решением из всех вариантов.
— Джон

94

Если вам нужно обрабатывать символы новой строки в разных системах, вы можете просто использовать предопределенную константу PHP PHP_EOL (http://php.net/manual/en/reserved.constants.php) и просто использовать explode, чтобы избежать накладных расходов на механизм регулярных выражений. ,

$lines = explode(PHP_EOL, $subject);

30
Осторожно: он будет работать в разных системах, но не будет работать со строками из разных систем . В руководстве по PHP указано, что PHP_EOL (string)это правильный символ «Конец строки» для этой платформы.
— wadim

@wadim прав! Если вы обрабатываете текстовый файл Windows на сервере Unix, это не удастся.
— javsmo 09

1
Помните, что в зависимости от длины ваших строк это может потреблять очень большой объем памяти для больших строк.
— Synchro

Обратите внимание, что если последняя строка содержит терминатор строки, то после этого также будет возвращена другая пустая строка.
— правый фолд

20

Это слишком сложно и некрасиво, но, на мой взгляд, это правильный путь:

$fp = fopen("php://memory", 'r+');
fputs($fp, $data);
rewind($fp);
while($line = fgets($fp)){
  // deal with $line
}
fclose($fp);

1
+1, и вы также можете использовать php://tempдля хранения больших данных во временном файле на диске.
— CodeAngry

4
Следует отметить, что это позволяет обнаруживать пустые строки, в отличие от решения strtok (). Документация находится по адресу php.net/manual/en/…
— Родин

7
foreach(preg_split('~[\r\n]+~', $text) as $line){
    if(empty($line) or ctype_space($line)) continue; // skip only spaces
    // if(!strlen($line = trim($line))) continue; // or trim by force and skip empty
    // $line is trimmed and nice here so use it
}

^ вот как правильно разбивать линии , кроссплатформенность совместима с Regexp:)


6

Возможные проблемы с памятью strtok :

Поскольку одно из предложенных решений использует strtok, к сожалению, оно не указывает на потенциальную проблему с памятью (хотя утверждает, что оно эффективно с точки зрения памяти). При использовании strtokсогласно инструкции :

Обратите внимание, что только первый вызов strtok использует строковый аргумент. Для каждого последующего вызова strtok нужен только токен, так как он отслеживает, где он находится в текущей строке.

Это происходит путем загрузки файла в память. Если вы используете большие файлы, вам нужно очистить их, если вы закончили цикл по файлу.

<?php
function process($str) {
    $line = strtok($str, PHP_EOL);

    /*do something with the first line here...*/

    while ($line !== FALSE) {
        // get the next line
        $line = strtok(PHP_EOL);

        /*do something with the rest of the lines here...*/

    }
    //the bit that frees up memory
    strtok('', '');
}

Если вас интересуют только физические файлы (например, сбор данных):

Согласно руководству , для загрузки файла вы можете использовать fileкоманду:

 //Create the array
 $lines = file( $some_file );

 foreach ( $lines as $line ) {
   //do something here.
 }

4

Ответ Кирилла лучше всего подходит, учитывая, что вам нужно иметь возможность обрабатывать новые строки на разных машинах.

«Я в основном ищу полезные функции PHP, а не алгоритм, как это сделать. Есть предложения?»

Я часто их использую:

  • explode () можно использовать для разделения строки на массив с одним разделителем.
  • implode () - аналог explode, позволяющий вернуться от массива к строке.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.