Scorrere su ogni riga di una stringa in PHP


130

Ho un modulo che consente all'utente di caricare un file di testo o di copiare / incollare il contenuto del file in un'area di testo. Posso facilmente distinguere tra i due e mettere quello che hanno inserito in una variabile stringa, ma dove devo andare da lì?

Devo scorrere su ogni riga della stringa (preferibilmente non preoccuparmi di newline su macchine diverse), assicurarmi che abbia esattamente un token (senza spazi, tabulazioni, virgole, ecc.), Disinfettare i dati, quindi generare una query SQL basato su tutte le linee.

Sono un programmatore abbastanza bravo, quindi conosco l'idea generale di come farlo, ma è passato tanto tempo da quando ho lavorato con PHP che sento di cercare cose sbagliate e quindi di trovare informazioni inutili. Il problema chiave che sto riscontrando è che voglio leggere il contenuto della stringa riga per riga. Se fosse un file, sarebbe facile.

Sto principalmente cercando utili funzioni PHP, non un algoritmo per come farlo. Eventuali suggerimenti?


Potresti voler prima normalizzare le nuove righe. Il metodo s($myString)->normalizeLineEndings()è disponibile con github.com/delight-im/PHP-Str (libreria con licenza MIT) che ha molti altri utili aiutanti di stringa. Potresti dare un'occhiata al codice sorgente.
Caw

Risposte:


190

preg_split la variabile che contiene il testo e scorre sull'array restituito:

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

Gestirà ^ M oltre a \ n \ r?
Topher Fangio,

Non sono sicuro che il ritorno a capo ascii venga convertito in \ r una volta inserito in una variabile. Altrimenti puoi sempre usare split () / exlope () con il valore ascii invece - ch (13)
Kyril,

12
Una regexp migliore è /((\r?\n)|(\r\n?))/.
Félix Saparelli,

3
Per abbinare Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) e raro LF + CR (\ n \ r) dovrebbe essere:/((\r?\n)|(\n?\r))/
Aspettando Dev ...

2
Questo probabilmente bombarderà catastroficamente per i dati multi-byte.
pguardiario,

158

Vorrei proporre un'alternativa significativamente più veloce (ed efficiente in termini di memoria): strtokpiuttosto che preg_split.

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

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

Testando le prestazioni, ho ripetuto 100 volte su un file di test con 17 mila righe: ci sono preg_splitvoluti 27,7 secondi, mentre ci sono strtokvoluti 1,4 secondi.

Nota che sebbene $separatorsia definito come "\r\n", strtoksi separerà su entrambi i caratteri e, a partire da PHP4.1.0, salta le righe / i token vuoti.

Vedere la voce del manuale strtok: http://php.net/strtok


21
+1 per considerazioni sulle prestazioni quando si tratta di set di linee di grandi dimensioni.
CodeAngry

4
Sebbene questa funzione API sia un disastro totale (chiamata con parametri diversi), questa è la soluzione migliore. Né prey_splitexplodedeve essere utilizzato a produrre frammenti di stringa strutturati. È come puntare a una mosca con un bazooka .
Maciej Sz,

1
Se controlli l'utilizzo della memoria mentre l'app è in esecuzione, vedrai la magia. In realtà estrae il file che stai leggendo in memoria nel caso in cui attraversi ciascuna delle linee e mantiene la posizione del token. Ti consigliamo di scaricarlo per essere veramente efficiente in termini di memoria. php.net/strtok#103051
AbsoluteƵERØ

2
breve nota, l'uso di strtok()qualcos'altro all'interno di quel whileciclo spezzerà le cose. Lo stavo anche usando per prendere tutto in una stringa fino al primo spazio ( stackoverflow.com/a/2477411/1767412 ) e mi ci è voluto un minuto per capire perché le cose non stessero andando come previsto
billynoah

1
dovrebbe essere la risposta accettata, probabilmente la soluzione più veloce tra tutte le opzioni.
Giovanni

94

Se hai bisogno di gestire newline in sistemi diversi puoi semplicemente usare la costante PHP_EOL predefinita PHP (http://php.net/manual/en/reserved.constants.php) e usare semplicemente esplodi per evitare il sovraccarico del motore delle espressioni regolari .

$lines = explode(PHP_EOL, $subject);

30
Attenzione: funzionerà su sistemi diversi ma non funzionerà bene con stringhe di sistemi diversi . Il Manuale di PHP afferma che PHP_EOL (string)è il simbolo 'Fine linea' corretto per questa piattaforma.
wadim,

@wadim ha ragione! Se stai elaborando un file di testo di Windows su un server Unix, fallirà.
javsmo,

1
Attenzione che, a seconda della lunghezza delle linee, questo può consumare grandi quantità di memoria per stringhe di grandi dimensioni.
Synchro

Nota che se l'ultima riga contiene un terminatore di riga, questo restituirà anche un'altra stringa vuota.
destra del

20

È eccessivamente complicato e brutto, ma secondo me questa è la strada da percorrere:

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

1
+1 e puoi anche usarlo php://tempper archiviare dati di grandi dimensioni in un file su disco temporaneo.
CodeAngry

4
Va notato che ciò consente di rilevare righe vuote, a differenza della soluzione strtok (). La documentazione è disponibile su php.net/manual/it/…
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
}

^ questo è il modo in cui interrompi correttamente le linee , compatibile multipiattaforma Regexp:)


6

Potenziali problemi di memoria con strtok :

Dal momento che una delle soluzioni suggerite utilizza strtok, purtroppo non indica un potenziale problema di memoria (sebbene affermi di essere efficiente in termini di memoria). Quando si utilizza strtoksecondo il manuale , il:

Nota che solo la prima chiamata a strtok utilizza l'argomento stringa. Ogni chiamata successiva a strtok richiede solo l'uso del token, in quanto tiene traccia di dove si trova nella stringa corrente.

Lo fa caricando il file in memoria. Se stai utilizzando file di grandi dimensioni, devi scaricarli se hai finito di scorrere il file.

<?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 ti occupi solo di file fisici (es. Datamining):

Secondo il manuale , per la parte di caricamento del file è possibile utilizzare il filecomando:

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

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

4

La risposta di Kyril è la migliore considerando che devi essere in grado di gestire le nuove linee su macchine diverse.

"Sono principalmente alla ricerca di utili funzioni PHP, non di un algoritmo per come farlo. Qualche suggerimento?"

Li uso molto:

  • explode () può essere usato per dividere una stringa in un array, dato un singolo delimitatore.
  • implode () è la controparte di explode, per tornare dall'array alla stringa.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.