Itérer sur chaque ligne dans une chaîne en PHP

130

J'ai un formulaire qui permet à l'utilisateur de télécharger un fichier texte ou de copier / coller le contenu du fichier dans une zone de texte. Je peux facilement faire la différence entre les deux et mettre celui qu'ils ont entré dans une variable de chaîne, mais où dois-je aller à partir de là?

Je dois parcourir chaque ligne de la chaîne (de préférence sans me soucier des retours à la ligne sur différentes machines), m'assurer qu'il contient exactement un jeton (pas d'espaces, tabulations, virgules, etc.), nettoyer les données, puis générer une requête SQL basé sur toutes les lignes.

Je suis un assez bon programmeur, donc je connais l'idée générale sur la façon de le faire, mais cela fait si longtemps que je n'ai pas travaillé avec PHP que j'ai l'impression de chercher les mauvaises choses et donc de trouver des informations inutiles. Le problème principal que j'ai est que je veux lire le contenu de la chaîne ligne par ligne. Si c'était un fichier, ce serait facile.

Je recherche principalement des fonctions PHP utiles, pas un algorithme pour savoir comment le faire. Aucune suggestion?

Topher Fangio
la source
Vous pouvez commencer par normaliser les nouvelles lignes. La méthode s($myString)->normalizeLineEndings()est disponible avec github.com/delight-im/PHP-Str (bibliothèque sous licence MIT) qui a beaucoup d'autres aides de chaîne utiles. Vous voudrez peut-être jeter un œil au code source.
caw

Réponses:

190

preg_split la variable contenant le texte, et itérer sur le tableau retourné:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 
Kyril
la source
Est-ce que cela gérera ^ M en plus de \ n \ r?
Topher Fangio le
Je ne suis pas sûr que le retour chariot ascii soit converti en \ r une fois qu'il est placé dans une variable. Sinon, vous pouvez toujours utiliser un split () / exlope () avec la valeur ascii à la place - ch (13)
Kyril
12
Une meilleure expression rationnelle est /((\r?\n)|(\r\n?))/.
Félix Saparelli
3
Pour correspondre à Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) et rare LF + CR (\ n \ r), il devrait être:/((\r?\n)|(\n?\r))/
En attente de Dev ...
2
Ceci est susceptible de bombarder de manière catastrophique pour les données multi-octets.
pguardiario
158

Je voudrais proposer une alternative beaucoup plus rapide (et efficace en mémoire): strtokplutôt que preg_split.

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

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

Tester les performances, j'ai itéré 100 fois sur un fichier de test de 17 mille lignes: cela a preg_splitpris 27,7 secondes, alors que cela a strtokpris 1,4 seconde.

Notez que bien que le $separatorsoit défini comme "\r\n", strtokse séparera sur l'un ou l'autre des caractères - et à partir de PHP4.1.0, sautez les lignes / jetons vides.

Voir l'entrée du manuel strtok: http://php.net/strtok

Erwin Wessels
la source
21
+1 pour des considérations de performances lors du traitement de grands ensembles de lignes.
CodeAngry
4
Bien que cette fonction api soit un désordre total (appel avec différents paramètres), c'est la meilleure solution. Ni prey_splitni explodene doivent être utilisés pour produire des fragments de chaîne structurés. C'est comme viser une mouche avec un bazooka .
Maciej Sz
1
Si vous vérifiez l'utilisation de la mémoire pendant que l'application est en cours d'exécution, vous verrez la magie. Il extrait en fait le fichier que vous lisez en mémoire au cas où vous parcourez chacune des lignes et conserve l'emplacement de votre jeton. Vous voudrez vider cela pour être vraiment efficace en mémoire. php.net/strtok#103051
AbsoluteƵERØ
2
note rapide, utiliser strtok()quelque chose d'autre dans cette whileboucle cassera les choses. Je l'utilisais également pour tout saisir dans une chaîne jusqu'au premier espace ( stackoverflow.com/a/2477411/1767412 ) et m'a pris une minute pour comprendre pourquoi les choses n'allaient pas comme prévu
billynoah
1
devrait être la réponse acceptée, probablement la solution la plus rapide parmi toutes les options.
John
94

Si vous avez besoin de gérer les nouvelles lignes dans différents systèmes, vous pouvez simplement utiliser la constante PHP prédéfinie PHP_EOL (http://php.net/manual/en/reserved.constants.php) et simplement utiliser exploser pour éviter la surcharge du moteur d'expression régulière .

$lines = explode(PHP_EOL, $subject);
FerCa
la source
30
Attention: cela fonctionnera sur différents systèmes, mais cela ne fonctionnera pas bien avec des chaînes de différents systèmes . Le manuel PHP indique que PHP_EOL (string)c'est le bon symbole 'End Of Line' pour cette plate - forme.
wadim
@wadim a raison! Si vous traitez un fichier texte Windows sur un serveur Unix, il échouera.
javsmo
1
Attention, selon la longueur de vos lignes, cela peut consommer de très grandes quantités de mémoire pour les grosses chaînes.
Synchro
Notez que si la dernière ligne contient un terminateur de ligne, cela renverra également une autre chaîne vide après cela.
droite
20

C'est trop compliqué et moche, mais à mon avis, c'est la voie à suivre:

$fp = fopen("php://memory", 'r+');
fputs($fp, $data);
rewind($fp);
while($line = fgets($fp)){
  // deal with $line
}
fclose($fp);
pguardiario
la source
1
+1 et vous pouvez également utiliser php://temppour stocker des données plus volumineuses dans un fichier de disque temporaire.
CodeAngry
4
Il est à noter que cela permet de détecter les lignes vides, contrairement à la solution strtok (). La documentation est sur 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
}

^ c'est ainsi que vous coupez correctement les lignes , compatible multiplateforme avec Regexp:)

CodeAngry
la source
6

Problèmes de mémoire potentiels avec strtok:

Étant donné que l'une des solutions suggérées est utilisée strtok, elle ne signale malheureusement pas un problème de mémoire potentiel (même si elle prétend être efficace en mémoire). Lors de l'utilisation strtokconformément au manuel , le:

Notez que seul le premier appel à strtok utilise l'argument chaîne. Chaque appel ultérieur à strtok n'a besoin que du jeton à utiliser, car il garde une trace de son emplacement dans la chaîne actuelle.

Il le fait en chargeant le fichier en mémoire. Si vous utilisez des fichiers volumineux, vous devez les vider si vous avez terminé de parcourir le fichier.

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

Si vous n'êtes concerné que par les fichiers physiques (par exemple, le datamining):

Selon le manuel , pour la partie téléchargement de fichier, vous pouvez utiliser la filecommande:

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

 foreach ( $lines as $line ) {
   //do something here.
 }
Zéro absolu
la source
4

La réponse de Kyril est la meilleure étant donné que vous devez être capable de gérer les nouvelles lignes sur différentes machines.

"Je recherche principalement des fonctions PHP utiles, pas un algorithme pour savoir comment le faire. Des suggestions?"

Je les utilise beaucoup:

  • explode () peut être utilisé pour diviser une chaîne en un tableau, avec un seul délimiteur.
  • implode () est l'équivalent d'explode, pour passer du tableau à la chaîne.
Joe Kiley
la source