2010-01-17 4 views
3

Pour mon implémentation de la commande tail shell sous Linux, j'ai besoin de lire un certain nombre de lignes/octets à la fin du fichier en utilisant l'entrée/la sortie du flux. Est-ce que quelqu'un a des suggestions sur la façon de le faire? Je soupçonne que j'ai besoin d'ouvrir un fichier et passer un paramètre au constructeur ifstream, mais je ne sais pas exactement quoi. Google n'a rien trouvé.Comment lire un nombre donné de lignes à la fin du fichier en utilisant des flux en C++?

+6

La bibliothèque standard n'a rien à faire - vous allez avoir à écrire du code. Et pour implémenter le drapeau -f de tail, vous devrez utiliser des trucs non standard. –

+0

Les flux ne sont pas conçus pour cela. Les flux sont destinés à la sérialisation (non-sérialisation des données textuelles). Il serait plus facile de descendre au code C. –

Répondre

2
#include <iostream> 
#include <fstream> 
#include <sstream> 

using namespace std; 

int main() 
{ 
    ifstream is("file.txt", ios::binary); 
    if (!is) { 
    cout << "Failed to open file" << endl; 
    return 1; 
    } 

    is.seekg(0, ios::end); 
    int len = is.tellg(); 
    char c; 
    int n = 0; 
    ostringstream line; 
    int lines = 0; 

    for (int i = len - 1; i >= 0; --i) { 
    is.seekg(i, ios::beg); 
    is.get(c); 
    if (c == '\n' || i == 0) { 
     if (i < len - 1) { 
     if (i == 0) { 
      line << c; 
     } 
     string s = line.str(); 
     cout << lines << ": " << string(s.rend() - n, s.rend()) << endl; 
     ++lines; 
     n = 0; 
     line.seekp(0, ios::beg); 
     } 
    } else { 
     line << c; 
     ++n; 
    } 
    } 

    is.close(); 

    return 0; 
} 
+1

recherche en arrière après le début du fichier (c'est-à-dire si le fichier a une taille inférieure à 4096 octets) est en fait non défini –

+0

cela ne cherche pas le point de départ – jspcal

2

Je ne pense pas qu'il y ait un moyen facile d'aller à ce sujet, vous aurez probablement besoin de chercher à la fin du fichier, sauvegardez un « morceau » (une taille arbitraire, mais quelques kilo-octets peut-être), lisez ce "morceau" de données et commencez à chercher de nouveaux caractères, si vous n'avez pas trouvé assez, vous sauvegardez deux fois votre taille de morceau (rappelez-vous, vous avez lu, donc vous devez sauvegarder le celui que vous lisez, plus celui que vous voulez lire ensuite), et lisez dans un autre.

HTH

4

Depuis la queue doit travailler avec des tuyaux, que vous ne pouvez pas revenir en arrière, vous devrez garder un tampon de rotation des n dernières lignes que vous avez lu que vous videra sur EOF.

+0

Cette méthode convient aux fichiers courts. Mais les fichiers volumineux nécessitent une technique différente. Vous devez chercher à la fin puis commencer à sauvegarder. –

+0

Vous pouvez utiliser différentes techniques pour différents fichiers, mais celui que j'ai décrit est toujours requis pour des choses comme stdin. – Tobu

+1

De plus, les devoirs n'ont pas besoin d'être performants, au moins, il ne l'a pas dit. – Potatoswatter

3

Ce problème est analogue au problème d'obtention des derniers noeuds n d'une liste à liaison unique. Vous devez aller tout le chemin jusqu'à la fin avec un tampon de n lignes, puis cracher les lignes de tampon.

0

cela montre comment vous le feriez en C++ ... lisez des morceaux successifs à la fin du fichier, puis scannez les morceaux pour trouver de nouvelles lignes. si un saut de ligne ne se trouve pas, une partie du morceau doit être maintenu autour et combiné avec le morceau suivant lu dans ...

// 
// USAGE: lastln COUNT [FILE] 
// 
// Print at most COUNT lines from the end of FILE or standard input. 
// If COUNT is -1, all lines are printed. 
// 

#include <errno.h> 
#include <libgen.h> 
#include <iostream> 
#include <fstream> 
#include <sstream> 

using namespace std; 

int main(int argc, char **argv) 
{ 
    int ret = 0, maxLines = -1, len, count = 0, sz = 4096, lines = 0, rd; 
    istream *is; 
    ifstream ifs; 
    stringstream ss; 
    char *buf = NULL; 
    const char *prog = (argc > 0 && argv[0] ? basename(argv[0]) : ""); 
    string line; 

    if (argc > 1) { 
    if ((maxLines = atoi(argv[1])) == 0) { 
     goto end; 
    } 
    } 

    if (argc > 2 && !(argv[2] && argv[2][0] == '-' && argv[2][1] == '\0')) { 
    ifs.open(argv[2], ios::in | ios::binary); 
    if (!ifs) { 
     ret = 1; 
     cerr << prog << ": " << argv[2] << ": " << strerror(errno) << endl; 
     goto end; 
    } 
    is = &ifs; 
    } else { 
    ss << cin.rdbuf(); 
    if (!ss) { 
     ret = 1; 
     cerr << prog << ": failed to read input" << endl; 
     goto end; 
    } 
    is = &ss; 
    } 

    is->seekg(0, ios::end); 
    len = is->tellg(); 
    buf = new char[sz + 1]; 

    while (rd = min(len - count, sz)) { 
    is->seekg(0 - count - rd, ios::end); 
    is->read(buf, rd); 
    count += rd; 
    char *p = buf + rd, *q; 
    *p = '\0'; 

    for (;;) { 
     q = (char *)memrchr(buf, '\n', p - buf); 
     if (q || count == len) { 
     if (q) *q = '\0'; 
     if (lines || p - q - 1 > 0 || !q) { 
      ++lines; 
      cout << lines << ": " << (q ? q + 1 : buf) << line << endl; 
      line.clear(); 
      if (lines >= maxLines && maxLines != -1) break; 
     } 
     if (q) p = q; else break; 
     } else { 
     line = string(buf, p - buf) + line; 
     break; 
     } 
    } 
    } 

    end: 

    if (buf) delete[] buf; 
    return ret; 
} 
+0

Ce programme utilise des pointeurs et la gestion manuelle de la mémoire sans raison. Le programme pourrait tout aussi bien utiliser std :: vector pour les données dynamiquement allouées et le stockage automatique std :: ifstream au lieu d'un pointeur et d'une allocation dynamique. Cela évite tout nettoyage à la fin, évitant ainsi l'utilisation de goto. Cela évite également une fuite de mémoire. – Tronic

+0

non, vous * avez besoin d'un pointeur * avec 'memrchr'. il * prend seulement un pointeur *. un vecteur serait une couche inutile d'indirection. un pointeur * est nécessaire * pour utiliser cin ici. vous semblez oublier que * les références ne peuvent pas être réajustées * en C++. goto est * nécessaire * pour que l'OP puisse imprimer des messages à la fin de la course. et il n'y a pas besoin de libérer des données juste avant la sortie(). De toute façon, vous êtes obsédé par le sucre syntaxique. – jspcal

+0

Je l'appellerais «préférer le code lisible», pas «obsédé par le sucre syntaxique». Ce code se lit plus comme C que C++ idiomatique. Vous pouvez obtenir un pointeur sur le contenu d'un 'std :: vector', à utiliser avec' memrchr', en appelant 'front()' et en prenant l'adresse. Le pointeur n'est * pas * nécessaire pour utiliser 'cin'; vous pouvez supporter à la fois stdin et noms de fichiers sur la ligne de commande, sans utiliser de pointeurs, en plaçant le code dans une fonction qui prend un paramètre 'istream &' et en lui passant soit std :: cin' ou une instance 'std :: ifstream' nécessaire. – Wyzard

Questions connexes