Iterar sobre cada linha em uma string no PHP

130

Eu tenho um formulário que permite ao usuário fazer upload de um arquivo de texto ou copiar / colar o conteúdo do arquivo em uma área de texto. Posso diferenciar facilmente os dois e colocar o que eles inseriram em uma variável de string, mas para onde vou a partir daí?

Eu preciso iterar sobre cada linha da string (de preferência não se preocupar com novas linhas em máquinas diferentes), garantir que ele tenha exatamente um token (sem espaços, tabulações, vírgulas etc.), higienizar os dados e gerar uma consulta SQL com base em todas as linhas.

Sou um bom programador, por isso conheço a idéia geral de como fazê-lo, mas faz tanto tempo desde que trabalhei com PHP que sinto que estou procurando as coisas erradas e, assim, obtendo informações inúteis. O principal problema que estou enfrentando é que quero ler o conteúdo da string linha por linha. Se fosse um arquivo, seria fácil.

Estou procurando principalmente funções úteis do PHP, não um algoritmo de como fazê-lo. Alguma sugestão?

Topher Fangio
fonte
Você pode normalizar as novas linhas primeiro. O método s($myString)->normalizeLineEndings()está disponível no github.com/delight-im/PHP-Str (biblioteca sob licença MIT), que possui muitos outros auxiliares de seqüência de caracteres úteis. Você pode dar uma olhada no código fonte.
caw

Respostas:

190

preg_split a variável que contém o texto e itere sobre a matriz retornada:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 
Kyril
fonte
Isso manipulará ^ M além de \ n \ r?
Topher Fangio
Não tenho certeza se o retorno de carro ASCII é convertido para \ r uma vez que é colocado dentro de uma variável. Se não você sempre pode usar um split () / exlope () com o valor ascii vez - ch (13)
Kyril
12
Um melhor regexp é /((\r?\n)|(\r\n?))/.
Félix Saparelli 12/11/11
3
Para combinar Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) e LF + CR raro (\ n \ r), deve ser:/((\r?\n)|(\n?\r))/
Aguardando Dev ...
2
É provável que seja bombardeado catastroficamente para dados de vários bytes.
Pguardiario 12/07/2013
158

Eu gostaria de propor uma alternativa significativamente mais rápida (e eficiente em termos de memória): em strtokvez de preg_split.

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

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

Testando o desempenho, iterei 100 vezes em um arquivo de teste com 17 mil linhas: preg_splitdemorou 27,7 segundos, enquanto strtokdemorou 1,4 segundos.

Note que, embora o $separatordefinido como "\r\n", strtokseja separado em qualquer caractere - e a partir do PHP4.1.0, pule linhas / tokens vazios.

Veja a entrada manual do strtok: http://php.net/strtok

Erwin Wessels
fonte
21
+1 para considerações de desempenho ao lidar com conjuntos de linhas grandes.
precisa
4
Embora essa função api seja uma bagunça total (chamada com parâmetros diferentes), esta é a melhor solução. Nem prey_splitnem explodedeve ser usado para a preparação de fragmentos de cordas estruturados. É como mirar uma mosca com uma bazuca .
Maciej Sz 25/03
1
Se você verificar o uso da memória enquanto o aplicativo estiver em execução, verá a mágica. Na verdade, ele puxa o arquivo que você está lendo para a memória no caso de você percorrer cada uma das linhas e mantém a localização do seu token. Você desejará liberar isso para ser realmente eficiente em termos de memória. php.net/strtok#103051
AbsoluteƵERØ
2
observação rápida, usar strtok()algo mais dentro desse whileloop quebrará as coisas. Eu também estava usando-o para tudo agarrar em uma corda até o primeiro espaço ( stackoverflow.com/a/2477411/1767412 ) e me levou um minuto para perceber por que as coisas não estavam indo como planejado
billynoah
1
deve ser a resposta aceita, provavelmente a solução mais rápida de todas as opções.
John John
94

Se você precisar lidar com novas linhas em sistemas diferentes, basta usar a constante PHP_EOL constante do PHP (http://php.net/manual/en/reserved.constants.php) e simplesmente usar explodir para evitar a sobrecarga do mecanismo de expressão regular .

$lines = explode(PHP_EOL, $subject);
FerCa
fonte
30
Cuidado: Ele funcionará em sistemas diferentes, mas não funcionará bem com cadeias de sistemas diferentes . O Manual do PHP declara que PHP_EOL (string)é o símbolo 'End Of Line' correto para esta plataforma.
Wadim
@wadim está certo! Se você estiver processando um arquivo de texto do Windows em um servidor Unix, ele falhará.
javsmo
1
Lembre-se de que, dependendo do comprimento das suas linhas, isso pode consumir grandes quantidades de memória para grandes seqüências de caracteres.
Synchro 03/03
Observe que, se a última linha contiver um terminador de linha, isso também retornará outra sequência vazia depois disso.
rightfold
20

É muito complicado e feio, mas na minha opinião, este é o caminho a seguir:

$fp = fopen("php://memory", 'r+');
fputs($fp, $data);
rewind($fp);
while($line = fgets($fp)){
  // deal with $line
}
fclose($fp);
pguardiario
fonte
1
+1 e você também pode usar php://temppara armazenar dados maiores em um arquivo de disco temporário.
precisa
4
Deve-se notar que isso permite detectar linhas vazias, diferentemente da solução strtok (). A documentação está em php.net/manual/en/…
Josip Rodin
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
}

^ é assim que você quebra as linhas corretamente , compatível com várias plataformas Regexp:)

CodeAngry
fonte
6

Problemas potenciais de memória com strtok:

Como uma das soluções sugeridas é usada strtok, infelizmente ela não indica um problema de memória em potencial (embora afirme ser eficiente em termos de memória). Ao usar de strtokacordo com o manual , o:

Observe que apenas a primeira chamada para strtok usa o argumento string. Todas as chamadas subseqüentes ao strtok precisam apenas do token para serem usadas, pois ele mantém o controle de onde está na string atual.

Isso é feito carregando o arquivo na memória. Se você estiver usando arquivos grandes, precisará liberá-los se terminar de percorrer o arquivo.

<?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('', '');
}

Se você está preocupado apenas com arquivos físicos (por exemplo, datamining):

De acordo com o manual , para a parte de upload de arquivo, você pode usar o filecomando:

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

 foreach ( $lines as $line ) {
   //do something here.
 }
Zero absoluto
fonte
4

A resposta de Kyril é melhor, considerando que você precisa lidar com novas linhas em máquinas diferentes.

"Estou procurando principalmente funções úteis do PHP, não um algoritmo sobre como fazê-lo. Alguma sugestão?"

Eu uso muito isso:

  • explode () pode ser usado para dividir uma string em uma matriz, considerando um único delimitador.
  • implode () é a contraparte de explodir, para ir do array de volta à string.
Joe Kiley
fonte