Iteruj po każdej linii w ciągu w PHP

137

Mam formularz, który pozwala użytkownikowi przesłać plik tekstowy lub skopiować / wkleić zawartość pliku do obszaru tekstowego. Mogę łatwo je rozróżnić i umieścić dowolną z nich w zmiennej łańcuchowej, ale dokąd mam się udać?

Muszę iterować po każdej linii ciągu (najlepiej nie martwić się o nowe linie na różnych komputerach), upewnić się, że ma dokładnie jeden token (bez spacji, tabulatorów, przecinków itp.), Oczyścić dane, a następnie wygenerować zapytanie SQL na podstawie wszystkich linii.

Jestem dość dobrym programistą, więc znam ogólny pomysł, jak to zrobić, ale minęło tak dużo czasu, odkąd pracowałem z PHP, że czuję, że szukam niewłaściwych rzeczy iw ten sposób znajduję bezużyteczne informacje. Głównym problemem, który mam, jest to, że chcę czytać zawartość ciągu wiersz po wierszu. Gdyby to był plik, byłoby łatwo.

Poszukuję głównie przydatnych funkcji PHP, a nie algorytmu, jak to zrobić. Jakieś sugestie?

Topher Fangio
źródło
Możesz najpierw znormalizować znaki nowej linii. Metoda s($myString)->normalizeLineEndings()jest dostępna na github.com/delight-im/PHP-Str (biblioteka na licencji MIT), która ma wiele innych przydatnych pomocników ciągów. Możesz rzucić okiem na kod źródłowy.
krakaj

Odpowiedzi:

194

preg_split zmienna zawierająca tekst i iteruj po zwróconej tablicy:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 
Kyril
źródło
Czy będzie to uchwyt ^ M oprócz \ n \ r?
Topher Fangio
Nie jestem pewien, czy znak powrotu karetki ascii zostanie przekonwertowany na \ r po umieszczeniu w zmiennej. Jeśli nie, zawsze możesz użyć split () / exlope () z wartością ascii - ch (13)
Kyril
12
Lepszym wyrażeniem regularnym jest /((\r?\n)|(\r\n?))/.
Félix Saparelli
3
Aby dopasować Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) i rzadkie LF + CR (\ n \ r) powinno być:/((\r?\n)|(\n?\r))/
Oczekiwanie na Dev ...
2
Może to spowodować katastrofalne skutki dla danych wielobajtowych.
pguardiario
162

Chciałbym zaproponować znacznie szybszą (i wydajną pod względem pamięci) alternatywę: strtokzamiast preg_split.

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

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

Testując wydajność, iterowałem 100 razy na pliku testowym z 17 tysiącami linii: preg_splitzajęło to 27,7 sekundy, a strtokzajęło 1,4 sekundy.

Zauważ, że chociaż $separatorjest zdefiniowane jako "\r\n", strtokbędzie się rozdzielać po każdym znaku - a od PHP 4.1.0, pomija puste linie / tokeny.

Zobacz podręcznik strtok: http://php.net/strtok

Erwin Wessels
źródło
21
+1 za względy wydajności w przypadku dużych zestawów linii.
CodeAngry,
4
Chociaż ta funkcja API to totalny bałagan (wywołanie z różnymi parametrami), jest to najlepsze rozwiązanie. Ani prey_splitani nie explodenależy używać do tworzenia strukturalnych fragmentów ciągów. To tak, jakby celować w muchę z bazooką .
Maciej Sz
1
Jeśli sprawdzisz użycie pamięci, gdy aplikacja jest uruchomiona, zobaczysz magię. W rzeczywistości pobiera plik, który czytasz do pamięci w przypadku, gdy przejdziesz przez każdą z linii i zachowuje lokalizację tokena. Będziesz chciał to opróżnić, aby być naprawdę wydajnym pamięcią. php.net/strtok#103051
AbsoluteƵERØ
2
uwaga, użycie strtok()czegoś innego wewnątrz tej whilepętli zepsuje wszystko. Używałem go również do złapania wszystkiego w łańcuch do pierwszej spacji ( stackoverflow.com/a/2477411/1767412 ) i zajęło mi chwilę, aby zrozumieć, dlaczego sprawy nie idą zgodnie z planem
billynoah
1
powinna być zaakceptowaną odpowiedzią, prawdopodobnie najszybszym rozwiązaniem ze wszystkich opcji.
Jan
95

Jeśli potrzebujesz obsługiwać znaki nowej linii w różnych systemach, możesz po prostu użyć predefiniowanej stałej PHP PHP_EOL (http://php.net/manual/en/reserved.constants.php) i po prostu użyć funkcji eksploduj, aby uniknąć narzutu silnika wyrażeń regularnych .

$lines = explode(PHP_EOL, $subject);
FerCa
źródło
31
Uwaga: będzie działać na różnych systemach, ale nie będzie działać dobrze z napisami z różnych systemów . W PHP Manual stwierdza, że PHP_EOL (string)jest prawidłowy „End Of Line” symbol tej platformy.
wadim,
@wadim ma rację! Jeśli przetwarzasz plik tekstowy Windows na serwerze Unix, to się nie powiedzie.
javsmo
1
Uważaj, w zależności od długości linii, może to pochłonąć bardzo dużo pamięci dla dużych ciągów.
Synchro
Zauważ, że jeśli ostatnia linia zawiera terminator linii, to po tym zwróci również kolejny pusty ciąg.
prawy bok
21

Jest to zbyt skomplikowane i brzydkie, ale moim zdaniem jest to droga:

$fp = fopen("php://memory", 'r+');
fputs($fp, $data);
rewind($fp);
while($line = fgets($fp)){
  // deal with $line
}
fclose($fp);
pguardiario
źródło
1
+1 i możesz również użyć php://tempdo przechowywania większych danych w tymczasowym pliku na dysku.
CodeAngry,
4
Należy zauważyć, że pozwala to wykryć puste wiersze, w przeciwieństwie do rozwiązania strtok (). Dokumentacja jest na 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
}

^ tak poprawnie przerywasz linie , kompatybilne z Regexpróżnymi platformami :)

CodeAngry
źródło
6

Potencjalne problemy z pamięcią strtok:

Ponieważ jedno z sugerowanych rozwiązań wykorzystuje strtok, niestety nie wskazuje na potencjalny problem z pamięcią (choć twierdzi, że jest wydajna pod względem pamięci). Podczas korzystania strtokwedług instrukcji , wyrażenie:

Zauważ, że tylko pierwsze wywołanie strtok używa argumentu string. Każde kolejne wywołanie strtok wymaga tylko tokena do użycia, ponieważ śledzi, gdzie się znajduje w bieżącym ciągu.

Robi to poprzez załadowanie pliku do pamięci. Jeśli używasz dużych plików, musisz je opróżnić, jeśli skończysz przeglądać plik w pętli.

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

Jeśli interesują Cię tylko pliki fizyczne (np. Przetwarzanie danych):

Zgodnie z instrukcją do części upload plików można użyć filepolecenia:

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

 foreach ( $lines as $line ) {
   //do something here.
 }
Zero absolutne
źródło
4

Odpowiedź Kyrila jest najlepsza, biorąc pod uwagę, że musisz umieć obsługiwać nowe linie na różnych maszynach.

„Szukam przede wszystkim przydatnych funkcji PHP, a nie algorytmu, jak to zrobić. Jakieś sugestie?”

Używam ich często:

  • Explode () może służyć do dzielenia ciągu znaków na tablicę, z podaniem pojedynczego separatora.
  • implode () jest odpowiednikiem explode, przechodzącym z tablicy z powrotem do łańcucha.
Joe Kiley
źródło