96 votes

Obtenir de std : : ifstream qu'il gère LF, CR et CRLF ?

Plus précisément, je suis intéressé par istream& getline ( istream& is, string& str ); . Existe-t-il une option dans le constructeur d'ifstream pour lui demander de convertir tous les encodages de nouvelles lignes en ' \n sous le capot ? Je veux pouvoir appeler getline et qu'il gère gracieusement toutes les fins de ligne.

Mise à jour : Pour clarifier, je veux être capable d'écrire du code qui compile presque n'importe où, et qui prend des données de presque n'importe où. Y compris les rares fichiers qui ont ' \r ' sans ' \n '. Minimiser les inconvénients pour les utilisateurs du logiciel.

Il est facile de contourner le problème, mais je suis toujours curieux de savoir quelle est la bonne façon, dans la norme, de gérer de manière flexible tous les formats de fichiers texte.

getline se lit sur une ligne complète, jusqu'à un '. \n dans une chaîne de caractères. La chaîne ' \n ' est consommé dans le flux, mais getline ne l'inclut pas dans la chaîne. Tout va bien jusqu'à présent, mais il se peut qu'il y ait un ' \r ' juste avant le ' \n qui est incluse dans la chaîne.

Il y a trois types de fin de ligne dans les fichiers texte : ' \n ' est la terminaison conventionnelle sur les machines Unix, ' \r était (je crois) utilisé sur les anciens systèmes d'exploitation Mac, et Windows utilise une paire, ' \r ' suivi de ' \n '.

Le problème est que getline laisse l'option ' \r ' à la fin de la chaîne.

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.
}

Editer Merci à Neil de l'avoir signalé f.good() n'est pas ce que je voulais. !f.fail() c'est ce que je veux.

Je peux les supprimer manuellement (voir l'édition de cette question), ce qui est facile pour les fichiers texte de Windows. Mais je crains que quelqu'un n'introduise un fichier contenant uniquement des ' \r '. Dans ce cas, je suppose que getline consommera tout le fichier, pensant qu'il s'agit d'une seule ligne !

et c'est sans compter l'Unicode :-)

peut-être que Boost a une bonne façon de consommer une ligne à la fois à partir de n'importe quel type de fichier texte ?

Editer Je l'utilise pour gérer les fichiers Windows, mais j'ai toujours l'impression que je ne devrais pas avoir à le faire ! Et ce n'est pas le cas pour les fichiers ' \r Fichiers 'uniquement'.

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

2 votes

\n signifie nouvelle ligne, quelle que soit la manière dont elle est présentée dans le système d'exploitation actuel. La bibliothèque s'en charge. Mais pour que cela fonctionne, un programme compilé sous Windows doit lire les fichiers texte de Windows, un programme compilé sous Unix, les fichiers texte d'Unix, etc.

1 votes

@George, même si je compile sur une machine Linux, j'utilise parfois des fichiers texte provenant d'une machine Windows. Je vais peut-être publier mon logiciel (un petit outil d'analyse de réseau), et je veux pouvoir dire aux utilisateurs qu'ils peuvent fournir presque n'importe quel type de fichier texte (de type ASCII).

119voto

user763305 Points 6123

Comme l'a souligné Neil, "le moteur d'exécution C++ devrait traiter correctement la convention de fin de ligne de votre plate-forme particulière".

Cependant, les gens déplacent des fichiers texte entre différentes plates-formes, ce qui n'est pas suffisant. Voici une fonction qui gère les trois fins de ligne (" \r ", " \n " et " \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;
        }
    }
}

Voici un programme de test :

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;
}

0 votes

C'est exactement ce dont j'ai besoin. Merci. Quelques questions pour m'aider à comprendre ; #1 quand j'exécute ceci sur un fichier test qui a /r pour les caractères EOL, j'obtiens une boucle infinie à moins que j'ajoute && (t != "")) dans la boucle while. J'aimerais savoir ce que je devrais faire différemment et #2, pourquoi la méthode renvoie-t-elle istream& si elle met à jour istream par référence dans la méthode arg ? Je vous remercie.

0 votes

@Miek : Il revient istream& afin d'être cohérent avec la std::getline l'interface. Le comportement que vous signalez avec ' \r Il s'agit probablement d'un bogue dans mon code ; j'y jetterai un coup d'œil.

0 votes

Merci de votre réponse. Ce message est en fait repris et discuté ici. stackoverflow.com/questions/9188126/ et ce problème est abordé de manière spécifique. J'ai mis en œuvre leur suggestion pour résoudre la boucle infinie et cela semble fonctionner. J'apprécierais une suggestion sur ce que devrait être le code pour le cas '/n' puisqu'ils mentionnent qu'il a maintenant besoin d'un code séparé du cas EOF ? Il semble qu'il soit déjà séparé. Juste vide.

10voto

nbt Points 15965

Le moteur d'exécution C++ devrait traiter correctement la convention de fin de ligne de votre plate-forme particulière. Plus précisément, ce code devrait fonctionner sur toutes les plates-formes :

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

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

Bien entendu, si vous traitez des fichiers provenant d'une autre plateforme, tout est à refaire.

Comme les deux plates-formes les plus courantes (Linux et Windows) terminent toutes deux les lignes par un caractère de nouvelle ligne, Windows le faisant précéder d'un retour chariot, il est possible d'examiner le dernier caractère de l'élément line dans le code ci-dessus pour voir s'il s'agit d'une chaîne de caractères. \r et, le cas échéant, le supprimer avant de procéder au traitement spécifique à l'application.

Par exemple, vous pourriez vous doter d'une fonction de type getline qui ressemblerait à ceci (non testé, utilisation d'index, de substr, etc. à des fins pédagogiques uniquement) :

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;
}

11 votes

La question est la suivante à propos de comment traiter les fichiers provenant d'une autre plate-forme.

4 votes

@Neil, cette réponse n'est pas encore suffisante. Si je voulais seulement gérer les CRLF, je ne serais pas venu sur StackOverflow. Le vrai défi est de gérer les fichiers qui sólo avoir \r '. Ils sont plutôt rares de nos jours, maintenant que MacOS s'est rapproché d'Unix, mais je ne veux pas supposer qu'ils ne seront jamais introduits dans mon logiciel.

1 votes

@Aaron Eh bien, si vous voulez être capable de gérer N'IMPORTE QUOI, vous devez écrire votre propre code pour le faire.

10voto

Lisez-vous le fichier dans BINAIRE ou en TEXTE mode ? En TEXTE la paire retour chariot/saut de ligne, CRLF est interprété comme TEXTE fin de ligne, ou caractère de fin de ligne, mais en BINAIRE vous n'obtenez que UN octet par octet, ce qui signifie que l'un ou l'autre caractère MUST est ignorée et laissée dans la mémoire tampon pour être récupérée comme un autre octet ! Le retour du chariot signifie, dans une machine à écrire, que le chariot de la machine à écrire, dans lequel se trouve le bras d'impression, a atteint le bord droit du papier et est ramené au bord gauche. Il s'agit d'un modèle très mécanique, celui de la machine à écrire mécanique. L'avance de la ligne signifie que le rouleau de papier est légèrement tourné vers le haut afin que le papier soit en position pour commencer une autre ligne de frappe. Pour autant que je me souvienne, l'un des chiffres les plus bas de l'ASCII signifie qu'il faut se déplacer vers la droite d'un caractère sans taper, le caractère mort, et bien sûr \b signifie retour en arrière : déplacer la voiture d'un caractère vers l'arrière. De cette façon, vous pouvez ajouter des effets spéciaux, comme le soulignement (type underscore), le barré (type minus), des accents différents approximatifs, l'annulation (type X), sans avoir besoin d'un clavier étendu, simplement en ajustant la position de la voiture le long de la ligne avant d'entrer le saut de ligne. Il est donc possible d'utiliser des tensions ASCII de la taille d'un octet pour contrôler automatiquement une machine à écrire sans ordinateur intermédiaire. Lorsque la machine à écrire automatique est introduite, AUTOMATIQUE signifie qu'une fois que vous avez atteint le bord le plus éloigné de la feuille, la voiture est renvoyée à gauche. ET le saut de ligne appliqué, c'est-à-dire que le wagon est supposé être renvoyé automatiquement au fur et à mesure que le rouleau se déplace vers le haut ! Vous n'avez donc pas besoin des deux caractères de contrôle, mais d'un seul, le caractère \n , nouvelle ligne ou saut de ligne.

Cela n'a rien à voir avec la programmation, mais l'ASCII est plus ancien et il semble que certaines personnes n'aient pas réfléchi lorsqu'elles ont commencé à faire du texte ! La plateforme UNIX suppose une machine à écrire automatique électrique ; le modèle Windows est plus complet et permet de contrôler des machines mécaniques, bien que certains caractères de contrôle deviennent de moins en moins utiles dans les ordinateurs, comme le caractère cloche, 0x07 si je me souviens bien... Certains textes oubliés ont dû être saisis à l'origine avec des caractères de contrôle pour des machines à écrire à commande électrique, ce qui a perpétué le modèle...

En fait, la variante correcte consisterait à inclure simplement l'élément \r , saut de ligne, le retour chariot n'étant pas nécessaire, c'est-à-dire automatique, donc :

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);
...

serait la manière la plus correcte de traiter tous les types de fichiers. Il convient toutefois de noter que \n en TEXTE est en fait la paire d'octets 0x0d 0x0a, mais 0x0d IS juste \r : \n comprend \r en TEXTE mais pas en mode BINAIRE Así que \n y \r\n sont équivalents... ou devraient l'être. Il s'agit en fait d'une confusion très fondamentale dans l'industrie, d'une inertie typique de l'industrie, puisque la convention est de parler de CRLF, dans TOUTES les plateformes, puis de tomber dans des interprétations binaires différentes. Strictement parlant, les fichiers comprenant UNIQUEMENT 0x0d (retour chariot) comme étant \n (CRLF ou saut de ligne), sont malformés en TEXTE (machine à écrire : il suffit de retourner la voiture et de tout barrer...), et sont un format binaire non orienté vers la ligne (soit \r o \r\n c'est-à-dire orienté vers la ligne) et n'est donc pas censé être lu comme du texte ! Le code devrait échouer, peut-être avec un message de l'utilisateur. Cela ne dépend pas seulement du système d'exploitation, mais aussi de l'implémentation de la bibliothèque C, ce qui ajoute à la confusion et aux variations possibles... (en particulier pour les couches de traduction UNICODE transparentes, qui ajoutent un autre point d'articulation pour des variations déroutantes).

Le problème de l'extrait de code précédent (machine à écrire mécanique) est qu'il est très inefficace s'il n'y a pas de \n caractères après \r (texte de la machine à écrire automatique). Ensuite, il suppose également BINAIRE où la bibliothèque C est forcée d'ignorer les interprétations de texte (locale) et de donner les octets purs. Il ne devrait pas y avoir de différence entre les deux modes en ce qui concerne les caractères de texte proprement dits, mais seulement les caractères de contrôle. BINAIRE est meilleur que TEXTE mode. Cette solution est efficace pour BINAIRE Le logiciel est inefficace pour les fichiers texte typiques du système d'exploitation Windows, indépendamment des variations de la bibliothèque C, et inefficace pour les formats de texte d'autres plates-formes (y compris les traductions de sites web en texte). Si vous vous souciez de l'efficacité, la solution consiste à utiliser un pointeur de fonction, à effectuer un test pour le mode \r vs \r\n Sélectionnez ensuite le meilleur code utilisateur getline dans le pointeur et invoquez-le à partir de celui-ci.

Par ailleurs, je me souviens avoir trouvé des \r\r\n ce qui se traduit par un texte à deux lignes, comme l'exigent encore certains consommateurs de textes imprimés.

0 votes

+1 pour "ios::binary" - parfois, vous voulez lire le fichier tel qu'il est (par exemple pour calculer une somme de contrôle, etc.) sans que le runtime ne change les fins de lignes.

3voto

user2061057 Points 468

Une solution consisterait à rechercher et à remplacer toutes les fins de ligne par ' \n ' - comme le fait par exemple Git par défaut.

1voto

Chrono Kitsune Points 2980

À part écrire votre propre gestionnaire personnalisé ou utiliser une bibliothèque externe, vous n'avez pas de chance. La chose la plus simple à faire est de vérifier que line[line.length() - 1] n'est pas ' \r '. Sous Linux, cela est superflu car la plupart des lignes se terminent par '. \n Vous perdrez donc pas mal de temps si vous vous trouvez dans une boucle. Sous Windows, c'est également superflu. Mais qu'en est-il des fichiers Mac classiques qui se terminent par ' \r ' ? std::getline ne fonctionnerait pas pour ces fichiers sous Linux ou Windows car ' \n et \r ' ' \n ' se terminent tous deux par \n ', ce qui élimine la nécessité de vérifier la présence de ' \r '. Il est évident qu'une tâche qui travaille avec ces fichiers ne fonctionnerait pas bien. Bien sûr, il y a aussi les nombreux systèmes EBCDIC, auxquels la plupart des bibliothèques n'oseront pas s'attaquer.

Vérification de la présence de ' \r est probablement la meilleure solution à votre problème. La lecture en mode binaire vous permettrait de vérifier les trois fins de ligne courantes (' \r ', ' \r\n et \n '). Si vous ne vous intéressez qu'à Linux et Windows, car les anciennes terminaisons de ligne de Mac ne devraient plus exister très longtemps, vérifiez la présence de ' \n ' uniquement et supprimez le ' \r Le caractère '.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X