Ulangi setiap baris dalam string di PHP

130

Saya memiliki formulir yang memungkinkan pengguna untuk mengunggah file teks atau menyalin / menempelkan konten file ke dalam textarea. Saya dapat dengan mudah membedakan antara keduanya dan menempatkan mana yang mereka masukkan ke dalam variabel string, tetapi ke mana saya pergi dari sana?

Saya perlu mengulangi setiap baris string (sebaiknya tidak khawatir tentang baris baru pada mesin yang berbeda), pastikan bahwa ia memiliki tepat satu token (tidak ada spasi, tab, koma, dll.), Membersihkan data, kemudian menghasilkan query SQL didasarkan dari semua lini.

Saya seorang programmer yang cukup baik, jadi saya tahu ide umum tentang bagaimana melakukannya, tetapi sudah lama saya bekerja dengan PHP sehingga saya merasa saya mencari hal-hal yang salah dan dengan demikian menghasilkan informasi yang tidak berguna. Masalah utama yang saya alami adalah bahwa saya ingin membaca isi string baris demi baris. Jika itu file, itu akan mudah.

Saya sebagian besar mencari fungsi PHP yang berguna, bukan algoritma untuk melakukannya. Ada saran?

Topher Fangio
sumber
Anda mungkin ingin menormalkan baris baru terlebih dahulu. Metode s($myString)->normalizeLineEndings()ini tersedia dengan github.com/delight-im/PHP-Str (perpustakaan di bawah Lisensi MIT) yang memiliki banyak pembantu string berguna lainnya. Anda mungkin ingin melihat kode sumbernya.
gak

Jawaban:

190

preg_split variabel yang berisi teks, dan beralih di atas array yang dikembalikan:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 
Kyril
sumber
Apakah ini menangani ^ M selain \ n \ r?
Topher Fangio
Saya tidak yakin apakah kembalinya ascii carriage akan dikonversi menjadi setelah ditempatkan di dalam variabel. Jika tidak, Anda selalu dapat menggunakan split () / exlope () dengan nilai ascii sebagai gantinya - ch (13)
Kyril
12
Regexp yang lebih baik adalah /((\r?\n)|(\r\n?))/.
Félix Saparelli
3
Untuk mencocokkan Unix LF (\ n), MacOS <9 CR (\ r), Windows CR + LF (\ r \ n) dan LF + CR langka (\ n \ r) seharusnya:/((\r?\n)|(\n?\r))/
Menunggu ...
2
Ini kemungkinan akan meledak secara serempak untuk data multi-byte.
pguardiario
158

Saya ingin mengusulkan alternatif yang jauh lebih cepat (dan hemat memori): strtokdaripada preg_split.

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

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

Menguji kinerja, saya mengulangi 100 kali lebih dari file uji dengan 17 ribu baris: preg_splitbutuh 27,7 detik, sedangkan strtokbutuh 1,4 detik.

Perhatikan bahwa meskipun $separatordidefinisikan sebagai "\r\n", strtokakan terpisah pada salah satu karakter - dan pada PHP4.1.0, lewati baris kosong / token.

Lihat entri manual strtok: http://php.net/strtok

Erwin Wessels
sumber
21
+1 untuk pertimbangan kinerja saat berhadapan dengan set garis besar.
CodeAngry
4
Meskipun fungsi api ini adalah kekacauan total (panggilan dengan parameter yang berbeda) ini adalah solusi terbaik. Tidak prey_splitjuga tidak explodeboleh digunakan untuk menghasilkan fragmen string terstruktur. Ini seperti membidik seekor lalat dengan bazoka .
Maciej Sz
1
Jika Anda memeriksa penggunaan memori saat aplikasi sedang berjalan, maka Anda akan melihat keajaibannya. Ini sebenarnya menarik file yang Anda baca ke dalam memori jika Anda mengulangi setiap baris, dan itu membuat lokasi token Anda. Anda harus menyiramnya agar benar-benar hemat memori. php.net/strtok#103051
AbsoluteƵERØ
2
catatan cepat, menggunakan strtok()sesuatu yang lain di dalam whileloop itu akan merusak banyak hal. Saya juga menggunakannya untuk mengambil semuanya dalam string hingga ke ruang pertama ( stackoverflow.com/a/2477411/1767412 ) dan butuh waktu sebentar untuk menyadari mengapa semuanya tidak berjalan seperti yang direncanakan
billynoah
1
harus menjadi jawaban yang diterima, mungkin solusi tercepat dari semua opsi.
John
94

Jika Anda perlu menangani baris baru dalam sistem yang berbeda, Anda cukup menggunakan konstanta PHP yang telah ditentukan, PHP_EOL (http://php.net/manual/en/reserved.constants.php) dan cukup menggunakan meledak untuk menghindari overhead mesin ekspresi reguler .

$lines = explode(PHP_EOL, $subject);
Ferco
sumber
30
Hati-hati: Ini akan bekerja pada sistem yang berbeda tetapi tidak akan bekerja dengan baik dengan string dari sistem yang berbeda . The PHP manual menyatakan bahwa PHP_EOL (string)adalah yang benar 'End Of Line simbol untuk ini platform yang.
wadim
@wadim benar! Jika Anda memproses file teks Windows di server Unix, itu akan gagal.
javsmo
1
Berhati-hatilah karena tergantung pada panjang baris Anda, ini bisa memakan memori yang sangat besar untuk string besar.
Sinkronisasi
Perhatikan bahwa jika baris terakhir berisi terminator garis, maka ini juga akan mengembalikan string kosong lain setelah itu.
sayap kanan
20

Ini terlalu rumit dan jelek tapi menurut saya ini adalah cara untuk pergi:

$fp = fopen("php://memory", 'r+');
fputs($fp, $data);
rewind($fp);
while($line = fgets($fp)){
  // deal with $line
}
fclose($fp);
pguardiario
sumber
1
+1 dan Anda juga dapat menggunakan php://tempuntuk menyimpan data yang lebih besar ke file disk sementara.
CodeAngry
4
Perlu dicatat bahwa ini memungkinkan Anda untuk mendeteksi garis kosong, tidak seperti solusi strtok (). Dokumentasinya ada di 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
}

^ beginilah cara Anda mematahkan garis dengan benar , kompatibel dengan lintas platform Regexp:)

CodeAngry
sumber
6

Masalah memori potensial dengan strtok:

Karena salah satu solusi yang disarankan digunakan strtok, sayangnya itu tidak menunjukkan masalah memori potensial (meskipun diklaim sebagai memori efisien). Bila menggunakan strtoksesuai dengan manual , yang:

Perhatikan bahwa hanya panggilan pertama ke strtok yang menggunakan argumen string. Setiap panggilan berikutnya ke strtok hanya perlu token untuk digunakan, karena melacak di mana ia berada di string saat ini.

Ini dilakukan dengan memuat file ke dalam memori. Jika Anda menggunakan file berukuran besar, Anda perlu membilasnya jika Anda sudah selesai memutarnya.

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

Jika Anda hanya peduli dengan file fisik (mis. Datamining):

Menurut manual , untuk bagian unggahan file Anda dapat menggunakan fileperintah:

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

 foreach ( $lines as $line ) {
   //do something here.
 }
AbsoluteƵERØ
sumber
4

Jawaban Kyril paling baik mengingat Anda harus dapat menangani baris baru pada mesin yang berbeda.

"Saya kebanyakan mencari fungsi PHP yang berguna, bukan algoritma untuk bagaimana melakukannya. Ada saran?"

Saya sering menggunakan ini:

  • explode () dapat digunakan untuk membagi string menjadi array, diberi pembatas tunggal.
  • implode () adalah pasangan meledak, untuk pergi dari array kembali ke string.
Joe Kiley
sumber