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

130

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

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

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

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

Topher Fangio
источник
Вы можете сначала нормализовать символы новой строки. Этот метод 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);
FerCa
источник
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);
pguardiario
источник
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:)

CodeAngry
источник
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, позволяющий вернуться от массива к строке.
Джо Кили
источник