Std :: ifstream LF, CR ve CRLF'yi işleyecek mi?


85

Özellikle ilgileniyorum istream& getline ( istream& is, string& str );. İfstream yapıcısına, tüm satırsonu kodlamalarını başlık altında "\ n" ye dönüştürmesini söyleme seçeneği var mı? getlineTüm satır sonlarını arayıp incelikle işleyebilmek istiyorum.

Güncelleme : Açıklığa kavuşturmak için, hemen hemen her yerde derlenen ve neredeyse her yerden girdi alan kod yazabilmek istiyorum. '\ N' içermeyen '\ r' içeren nadir dosyalar dahil. Yazılımın herhangi bir kullanıcısı için rahatsızlığı en aza indirme.

Sorunu çözmek kolaydır, ancak yine de tüm metin dosyası formatlarını esnek bir şekilde ele almak için standart olarak doğru yolu merak ediyorum.

getlinebir dizeye '\ n' kadar tam bir satır okur. '\ N' akımdan tüketilir, ancak getline onu dizeye dahil etmez. Şimdiye kadar sorun yok, ancak dizeye dahil edilen '\ n' öğesinden hemen önce bir '\ r' olabilir.

Orada satır sonları üç tür metin dosyaları görülen: '\ n' Unix makinelerde '\ r' geleneksel biten (Bence) eski Mac işletim sistemlerinde kullanılan ve Windows bir çift, '\ r' kullanır oldu ardından "\ n".

Sorun, getlinedizenin sonunda '\ r' bırakmasıdır.

ifstream f("a_text_file_of_unknown_origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
   // BUT, there might be an '\r' at the end now.
}

Düzenleme Neil'e f.good()istediğim şeyin bu olmadığını söylediği için teşekkürler . !f.fail()istediğim şey.

Windows metin dosyaları için kolaylıkla kendim kaldırabilirim (bu sorunun düzenlemesine bakın). Ama birinin sadece '\ r' içeren bir dosyayı besleyeceğinden endişeleniyorum. Bu durumda, getline'ın tek bir satır olduğunu düşünerek tüm dosyayı tüketeceğini varsayıyorum!

.. ve bu Unicode'u dikkate almıyor :-)

.. belki Boost'un herhangi bir metin dosyası türünden bir seferde bir satırı kullanmanın güzel bir yolu vardır?

Düzenle Bunu Windows dosyalarını işlemek için kullanıyorum, ancak yine de yapmamam gerektiğini düşünüyorum! Ve bu sadece '\ r' dosyaları için çatallaşmaz.

if(!line.empty() && *line.rbegin() == '\r') {
    line.erase( line.length()-1, 1);
}

2
\ n, mevcut işletim sisteminde sunulan her şekilde yeni satır anlamına gelir. Kütüphane bununla ilgilenir. Ancak bunun işe yaraması için, pencerelerde derlenen bir programın metin dosyalarını pencerelerden,
unix'te

1
@George, bir Linux makinesinde derlememe rağmen, bazen orijinal olarak bir Windows makinesinden gelen metin dosyalarını kullanıyorum. Yazılımımı (ağ analizi için küçük bir araç) yayınlayabilirim ve kullanıcılara hemen hemen her zaman (ASCII benzeri) metin dosyası besleyebileceklerini söylemek istiyorum.
Aaron McDaid


1
Eğer (f.good ()) düşündüğünüz şeyi yapmazsa unutmayın.

1
@JonathanMee: Şöyle olabilir bu . Olabilir.
Orbit'te Hafiflik Yarışları

Yanıtlar:


111

Neil'in belirttiği gibi, "C ++ çalışma zamanı, belirli platformunuz için satır sonlandırma kuralı ne olursa olsun doğru şekilde çalışmalıdır."

Ancak, insanlar metin dosyalarını farklı platformlar arasında taşıyor, bu yüzden bu yeterince iyi değil. Üç satır sonunu da işleyen bir işlev ("\ r", "\ n" ve "\ r \ n"):

std::istream& safeGetline(std::istream& is, std::string& t)
{
    t.clear();

    // The characters in the stream are read one-by-one using a std::streambuf.
    // That is faster than reading them one-by-one using the std::istream.
    // Code that uses streambuf this way must be guarded by a sentry object.
    // The sentry object performs various tasks,
    // such as thread synchronization and updating the stream state.

    std::istream::sentry se(is, true);
    std::streambuf* sb = is.rdbuf();

    for(;;) {
        int c = sb->sbumpc();
        switch (c) {
        case '\n':
            return is;
        case '\r':
            if(sb->sgetc() == '\n')
                sb->sbumpc();
            return is;
        case std::streambuf::traits_type::eof():
            // Also handle the case when the last line has no line ending
            if(t.empty())
                is.setstate(std::ios::eofbit);
            return is;
        default:
            t += (char)c;
        }
    }
}

Ve işte bir test programı:

int main()
{
    std::string path = ...  // insert path to test file here

    std::ifstream ifs(path.c_str());
    if(!ifs) {
        std::cout << "Failed to open the file." << std::endl;
        return EXIT_FAILURE;
    }

    int n = 0;
    std::string t;
    while(!safeGetline(ifs, t).eof())
        ++n;
    std::cout << "The file contains " << n << " lines." << std::endl;
    return EXIT_SUCCESS;
}

1
@Miek: Bo Persons öneri stackoverflow.com/questions/9188126/… ardından kodu güncelledim ve bazı testler yaptım . Artık her şey olması gerektiği gibi çalışıyor.
Johan Råde

1
@Thomas Weller: Nöbetçi için kurucu ve yıkıcı idam edildi. Bunlar, iş parçacığı senkronizasyonu, beyaz alanı atlama ve akış durumunu güncelleme gibi şeyler yapar.
Johan Råde

1
EOF durumunda, teofbit'i ayarlamadan önce boş olup olmadığını kontrol etmenin amacı nedir? Okunan diğer karakterlerden bağımsız olarak bu bitin ayarlanması gerekmez mi?
Yay295

1
Yay295: eof bayrağı, son satırın sonuna geldiğinizde değil, son satırın ötesini okumaya çalıştığınızda ayarlanmalıdır. Kontrol, son satırda EOL olmadığında bunun olmasını sağlar. (Kontrolü kaldırmayı deneyin ve ardından test programını son satırda EOL olmayan metin dosyasında çalıştırın ve göreceksiniz.)
Johan Råde

3
Bu da boş geçen çizgi, okur değil davranışı std::get_linebir boş son satırı görmezden hangi. std::get_lineDavranışı taklit etmek için eof durumunda aşağıdaki kodu kullandım :is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
Patrick Roocks

11

C ++ çalışma zamanı, belirli platformunuz için son hat kuralı ne olursa olsun doğru şekilde ilgilenmelidir. Özellikle, bu kod tüm platformlarda çalışmalıdır:

#include <string>
#include <iostream>
using namespace std;

int main() {
    string line;
    while( getline( cin, line ) ) {
        cout << line << endl;
    }
}

Elbette, başka bir platformdaki dosyalarla uğraşıyorsanız, tüm bahisler kapalıdır.

En yaygın iki platform (Linux ve Windows) her ikisi de satırları bir satırsonu karakteri ile sonlandırdığından, Windows ondan önce bir satır başı ile sonlandırıldığında, line öyle olmadığını görmek için yukarıdaki kodda dize \rve eğer öyleyse uygulamaya özel işlemlerinizi yapmadan önce onu kaldırın.

Örneğin, kendinize şuna benzer bir getline stili işlevi sağlayabilirsiniz (test edilmemiştir, yalnızca pedagojik amaçlar için dizin, alt öğe vb. Kullanımı):

ostream & safegetline( ostream & os, string & line ) {
    string myline;
    if ( getline( os, myline ) ) {
       if ( myline.size() && myline[myline.size()-1] == '\r' ) {
           line = myline.substr( 0, myline.size() - 1 );
       }
       else {
           line = myline;
       }
    }
    return os;
}

9
Soru, başka bir platformdaki dosyalarla nasıl başa çıkılacağıyla ilgili .
Yörüngede Hafiflik Yarışları

4
@Neil, bu cevap henüz yeterli değil. CRLF'leri işlemek isteseydim, StackOverflow'a gelmezdim. Gerçek zorluk, yalnızca '\ r' içeren dosyaları işlemektir . MacOS Unix'e yaklaştığı için günümüzde oldukça nadir görülüyor, ancak yazılımıma asla beslenmeyeceklerini varsaymak istemiyorum.
Aaron McDaid

1
@Aaron, HERHANGİ BİR ŞEYİ idare edebilmek istiyorsanız, bunu yapmak için kendi kodunuzu yazmalısınız.

4
Soruma başından beri bunu çözmenin kolay olduğunu belirterek, bunu yapmak istediğimi ve yapabileceğimi ima ettim. Bunu sordum çünkü çok yaygın bir soru gibi görünüyor ve çeşitli metin dosyası biçimleri var. Bunu C ++ standartları komitesinin oluşturduğunu varsaydım / umdum. Bu benim sorumdu.
Aaron McDaid

1
@Neil, sanırım unuttuğum başka bir konu var. Ama önce, desteklenecek az sayıda biçimi belirlemenin benim için pratik olduğunu kabul ediyorum. Bu nedenle, Windows ve Linux üzerinde derlenecek ve her iki formatta da çalışacak kod istiyorum. Sizin safegetline, çözümün önemli bir parçasıdır. Ancak bu program Windows üzerinde derleniyorsa, dosyayı ikili biçimde de açmam gerekecek mi? Windows derleyicileri (metin modunda) '\ n'nin' \ r '' \ n 'gibi davranmasına izin veriyor mu? ifstream f("f.txt", ios_base :: binary | ios_base::in );
Aaron McDaid

8

Eğer dosyayı okuyorsun BINARY veya METİN modunda? Gelen METİN modu çifti satırbaşı / satır besleme, CRLF , olarak yorumlanır METİN satırın sonuna veya satır karakteri sonunda, ancak BINARY sadece getirme TEK , bir anda hangi araçları byte bu da karakter MUTLAKAdikkate alınmaz ve başka bir bayt olarak getirilmek üzere arabellekte bırakılır! Satır dönüşü, daktiloda, baskı kolunun bulunduğu daktilo arabasının kağıdın sağ kenarına ulaştığı ve sol kenarına geri döndüğü anlamına gelir. Bu mekanik daktilonun çok mekanik bir modelidir. Daha sonra satır besleme, kağıt rulosunun biraz yukarı döndürüldüğü ve böylece kağıdın başka bir yazma satırına başlayacak konumda olduğu anlamına gelir. ASCII'deki düşük rakamlardan birini hatırladığım kadarıyla, yazı yazmadan sağa bir karakter, ölü karakter ve tabii ki \ b geri boşluk anlamına geliyor: arabayı bir karakter geri hareket ettir. Bu şekilde, genişletilmiş bir klavyeye ihtiyaç duymadan altta yatan (alt çizgi yazın), üstü çizili (eksi tür), farklı aksanları yaklaşık olarak iptal etme (X türü) gibi özel efektler ekleyebilirsiniz, satır beslemesine girmeden önce hat boyunca arabanın konumunu ayarlayarak. Böylece, arada bir bilgisayar olmadan bir daktiloyu otomatik olarak kontrol etmek için bayt boyutlu ASCII voltajlarını kullanabilirsiniz. Otomatik daktilo tanıtıldığında,OTOMATİK , kağıdın en uzak kenarına ulaştığınızda aracın sola dönmesi VE satır beslemesinin uygulandığı, yani rulo yukarı hareket ederken arabanın otomatik olarak geri döneceği varsayımı anlamına gelir! Bu nedenle, her iki kontrol karakterine de ihtiyacınız yoktur, yalnızca bir, \ n, yeni satır veya satır beslemesi gerekir.

Bunun programlama ile ilgisi yok ama ASCII daha eski ve HEY! Görünüşe göre bazı insanlar metin şeyler yapmaya başladıklarında düşünmüyorlardı! UNIX platformu, elektrikli bir otomatik tip makineyi varsayar; Windows modeli daha eksiksizdir ve mekanik makinelerin kontrolüne izin verir, ancak bazı kontrol karakterleri bilgisayarlarda gittikçe daha az kullanışlı hale gelse de, çan karakteri gibi, 0x07, iyi hatırlıyorsam ... Bazı unutulmuş metinler orijinal olarak kontrol karakterleriyle yakalanmış olmalı elektrikle kontrol edilen daktilolar için ve modeli sürdürdü ...

Aslında doğru varyasyon, satır beslemesini dahil etmek olacaktır, satır başı gereksizdir, yani otomatiktir, dolayısıyla:

char c;
ifstream is;
is.open("",ios::binary);
...
is.getline(buffer, bufsize, '\r');

//ignore following \n or restore the buffer data
if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c);
...

her tür dosyayı işlemenin en doğru yolu olacaktır. Not Ancak n o \ METİN modu aslında bayt çifti 0x0D 0x0a olmakla 0x0D IS sadece \ r \ n içinde \ r içerir METİN modunda ama değil BİNARY ... yani \ n ve \ r \ n eşdeğerdir ya olmalı. Bu çok temel bir endüstri karmaşası aslında, tipik endüstri ataleti, çünkü kongre TÜM platformlarda CRLF'den bahsediyor, sonra farklı ikili yorumlara düşüyor. Açıkçası, SADECE 0x0d (satır başı) olarak \ n (CRLF veya satır besleme) içeren dosyalar, METİNmodu (daktilo makinesi: sadece arabayı döndür ve her şeyin üstünü çiz ...) ve satır yönelimli olmayan ikili biçimdir (ya \ r ya da \ r \ n satır yönelimli anlamına gelir), bu yüzden metin olarak okumaman gerekmiyor! Kodun bir kullanıcı mesajıyla başarısız olması gerekir. Bu sadece işletim sistemine bağlı değildir, aynı zamanda kafa karışıklığına ve olası varyasyonlara katkıda bulunan C kütüphanesi uygulamasına da bağlıdır ... (özellikle kafa karıştırıcı varyasyonlar için başka bir eklem noktası ekleyen şeffaf UNICODE çeviri katmanları için).

Önceki kod parçacığı (mekanik daktilo) ile ilgili sorun, \ r (otomatik daktilo metni) 'den sonra \ n karakter yoksa çok verimsiz olmasıdır. Daha sonra , C kitaplığının metin yorumlarını (yerel ayar) yok saymaya ve tam baytları dağıtmaya zorlandığı BINARY modunu da varsayar . Her iki mod arasında gerçek metin karakterleri arasında hiçbir fark olmamalıdır, yalnızca kontrol karakterlerinde, bu nedenle genel olarak BINARY okuma , METİN modundan daha iyidir . Bu çözüm, BINARYC kitaplık varyasyonlarından bağımsız olarak tipik Windows işletim sistemi metin dosyaları modu ve diğer platform metin biçimleri için verimsiz (metne web çevirileri dahil). Verimliliği önemsiyorsanız, gitmenin yolu bir işlev işaretçisi kullanmaktır, \ r vs \ r \ n satır kontrolleri için istediğiniz gibi bir test yapın, ardından işaretçiye en iyi getline kullanıcı kodunu seçin ve onu buradan çağırın. o.

Bu arada ben de bazı metin dosyaları bulduğumu hatırlıyorum ... bu da bazı basılı metin tüketicileri için hala gerekli olduğu gibi çift satırlı metne çevrilir.


"İos :: binary" için +1 - bazen, çalışma zamanı satır sonlarını değiştirmeden dosyayı olduğu gibi okumak istersiniz (örneğin bir sağlama toplamı hesaplamak için vb.).
Matthias

2

Çözümlerden biri, ilk olarak tüm satır sonlarını aramak ve "\ n" ile değiştirmek olabilir - örneğin Git'in varsayılan olarak yaptığı gibi.


1

Kendi özel işleyicinizi yazmak veya harici bir kitaplık kullanmak dışında, şansınız kalmaz. Yapılması en kolay şey, emin olmak için kontrol etmektir.line[line.length() - 1] "\ r" olmadığından . Linux'ta, çoğu satırın sonunda '\ n' ile biteceği için bu gereksizdir, yani eğer bu bir döngüdeyse biraz zaman kaybedersiniz. Windows'ta bu da gereksizdir. Ancak, '\ r' ile biten klasik Mac dosyaları ne olacak? std :: getline, Linux veya Windows'ta bu dosyalar için çalışmaz çünkü '\ n' ve '\ r' '\ n' her ikisi de '\ n' ile biter ve '\ r' için kontrol etme ihtiyacını ortadan kaldırır. Açıkçası bu dosyalarla çalışan böyle bir görev iyi sonuç vermeyecektir. Tabii ki, o zaman çok sayıda EBCDIC sistemi var, bu çoğu kütüphanenin üstesinden gelmeye cesaret edemeyeceği bir şey.

"\ R" yi kontrol etmek muhtemelen sorununuz için en iyi çözümdür. İkili modda okumak, üç ortak satır sonunu ('\ r', '\ r \ n' ve '\ n') kontrol etmenizi sağlar. Eski tarz Mac satır sonları çok uzun süre ortalıkta olmadığı için sadece Linux ve Windows'u önemsiyorsanız, sadece "\ n" olup olmadığını kontrol edin ve sondaki "\ r" karakterini kaldırın.


0

Her satırda kaç tane öğe / numara olduğu biliniyorsa, bir satır örneğin 4 numara

string num;
is >> num >> num >> num >> num;

Bu, diğer satır sonlarıyla da çalışır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.