2010-10-13 3 views
4

Je tire des données d'un flux bzip2 dans une application C. Comme des morceaux de données sortent du décompresseur, ils peuvent être écrits stdout:Comment gérer un flux de données interne à une application basée sur C?

fwrite(buffer, 1, length, stdout); 

Cela fonctionne très bien. Je reçois toutes les données quand il est envoyé au stdout.

au lieu d'écrire stdout, je voudrais traiter la sortie de cette déclaration en interne dans une ligne-morceaux: une chaîne qui se termine par un retour à la ligne \n. Est-ce que j'écris la sortie du flux de décompresseur dans un autre tampon, un caractère à la fois, jusqu'à ce que j'atteigne une nouvelle ligne, puis j'appelle la fonction de traitement par ligne? Est-ce lent et y a-t-il une approche plus intelligente? Merci pour vos conseils.

EDIT

Merci pour vos suggestions. J'ai fini par créer une paire de tampons qui stockent le reste (le "stub" à la fin d'un tampon de sortie) au début d'un tampon de ligne courte, chaque fois que je passe à travers la valeur de données du tampon de sortie.

Je boucle le caractère de sortie caractère par caractère et traite la valeur d'une ligne de nouvelle ligne à la fois. Le reste sans nouvelle ligne est alloué et affecté et copié dans le tampon de ligne du flux suivant. Il semble que realloc est moins cher que les déclarations répétées malloc-free.

Voici le code que je suis venu avec:

char bzBuf[BZBUFMAXLEN]; 
BZFILE *bzFp; 
int bzError, bzNBuf; 
char bzLineBuf[BZLINEBUFMAXLEN]; 
char *bzBufRemainder = NULL; 
int bzBufPosition, bzLineBufPosition; 

bzFp = BZ2_bzReadOpen(&bzError, *fp, 0, 0, NULL, 0); /* http://www.bzip.org/1.0.5/bzip2-manual-1.0.5.html#bzcompress-init */ 

if (bzError != BZ_OK) { 
    BZ2_bzReadClose(&bzError, bzFp); 
    fprintf(stderr, "\n\t[gchr2] - Error: Bzip2 data could not be retrieved\n\n"); 
    return -1;   
} 

bzError = BZ_OK; 
bzLineBufPosition = 0; 
while (bzError == BZ_OK) { 

    bzNBuf = BZ2_bzRead(&bzError, bzFp, bzBuf, sizeof(bzBuf)); 

    if (bzError == BZ_OK || bzError == BZ_STREAM_END) { 
     if (bzBufRemainder != NULL) { 
      /* fprintf(stderr, "copying bzBufRemainder to bzLineBuf...\n"); */ 
      strncpy(bzLineBuf, bzBufRemainder, strlen(bzBufRemainder)); /* leave out \0 */ 
      bzLineBufPosition = strlen(bzBufRemainder); 
     } 

     for (bzBufPosition = 0; bzBufPosition < bzNBuf; bzBufPosition++) { 
      bzLineBuf[bzLineBufPosition++] = bzBuf[bzBufPosition]; 
      if (bzBuf[bzBufPosition] == '\n') { 
       bzLineBuf[bzLineBufPosition] = '\0'; /* terminate bzLineBuf */ 

       /* process the line buffer, e.g. print it out or transform it, etc. */ 
       fprintf(stdout, "%s", bzLineBuf); 

       bzLineBufPosition = 0; /* reset line buffer position */ 
      } 
      else if (bzBufPosition == (bzNBuf - 1)) { 
       bzLineBuf[bzLineBufPosition] = '\0'; 
       if (bzBufRemainder != NULL) 
        bzBufRemainder = (char *)realloc(bzBufRemainder, bzLineBufPosition); 
       else 
        bzBufRemainder = (char *)malloc(bzLineBufPosition); 
       strncpy(bzBufRemainder, bzLineBuf, bzLineBufPosition); 
      } 
     } 
    } 
} 

if (bzError != BZ_STREAM_END) { 
    BZ2_bzReadClose(&bzError, bzFp); 
    fprintf(stderr, "\n\t[gchr2] - Error: Bzip2 data could not be uncompressed\n\n"); 
    return -1; 
} else { 
    BZ2_bzReadGetUnused(&bzError, bzFp, 0, 0); 
    BZ2_bzReadClose(&bzError, bzFp); 
} 

free(bzBufRemainder); 
bzBufRemainder = NULL; 

J'apprécie vraiment l'aide de tout le monde. Cela fonctionne bien.

+0

strtok pourrait vous éviter de vérifier le retour à la ligne sur chaque caractère dans le tampon. – Tumas

+0

'strtok' est ce que j'ai essayé précédemment, mais cela ne fonctionne pas pour moi et je perds des données, car le tampon de sortie sortant du décompresseur bzip2 est divisé sur une ligne. Je perds ce peu à la fin. Peut-être que je l'utilise incorrectement. Y a-t-il une utilisation de 'strtok' qui permet de garder le" stub "sans nouvelle ligne, en collant ce bout au début du morceau de tampon de sortie suivant? –

+0

Non, vous avez raison, strtok ne correspond pas tout à fait ici. Vous perdez ce bit si vous utilisez strtok et il est probablement plus facile de vérifier chaque caractère. Je ne connais aucun moyen astucieux d'enregistrer ce "stub" sans nouvelle ligne pour une utilisation ultérieure, sauf pour le faire manuellement, comme l'a décrit Opera. Désolé, pour vous tromper un peu :) – Tumas

Répondre

1

Ce serait facile à faire en utilisant le std::string de C++, mais en C il prend du code si vous voulez le faire efficacement (sauf si vous utilisez une bibliothèque de chaînes dynamique).

char *bz_read_line(BZFILE *input) 
{ 
    size_t offset = 0; 
    size_t len = CHUNK; // arbitrary 
    char *output = (char *)xmalloc(len); 
    int bzerror; 

    while (BZ2_bzRead(&bzerror, input, output + offset, 1) == 1) { 
     if (offset+1 == len) { 
      len += CHUNK; 
      output = xrealloc(output, len); 
     } 
     if (output[offset] == '\n') 
      break; 
     offset++; 
    } 

    if (output[offset] == '\n') 
     output[offset] = '\0'; // strip trailing newline 
    else if (bzerror != BZ_STREAM_END) { 
     free(output); 
     return NULL; 
    } 

    return output; 
} 

(.. Où xmalloc et xrealloc erreurs de poignée interne Ne pas oublier de free la chaîne retournée)

C'est presque un ordre de grandeur plus lent que bzcat:

[email protected]:/tmp$ wc foo 
1193 5841 42868 foo 
[email protected]:/tmp$ bzip2 foo 
[email protected]:/tmp$ time bzcat foo.bz2 > /dev/null 

real 0m0.010s 
user 0m0.008s 
sys  0m0.000s 
[email protected]:/tmp$ time ./a.out <foo.bz2> /dev/null 

real 0m0.093s 
user 0m0.044s 
sys  0m0.020s 

Décider pour vous-même si c'est acceptable.

+0

J'ai un tas de flux bz2 concaténés dans un très gros fichier. J'essaie d'écrire une application autonome pour décompresser un flux parmi d'autres. C'est très utile, merci! –

2

Je ne pense pas qu'il existe une approche plus intelligente (sauf trouver une bibliothèque d'automates qui le fait déjà pour vous). Veillez à allouer la taille appropriée pour le tampon de «dernière ligne»: s'il ne peut pas gérer une longueur arbitraire et que l'entrée provient d'un élément accessible à des tiers, cela devient un risque pour la sécurité.

0

Je pense que vous devriez copier des morceaux de caractères dans un autre tampon jusqu'à ce que le dernier morceau que vous écrivez contienne un nouveau caractère de ligne. Ensuite, vous pouvez travailler sur toute la ligne.

Vous pouvez enregistrer le reste du tampon (après le '\n') dans un tampon temporaire, puis créer une nouvelle ligne à partir de celui-ci.

2

J'ai également travaillé sur le traitement des données bzip2 par ligne, et j'ai trouvé que lire un octet à la fois était trop lent. Cela a mieux fonctionné pour moi:

#include <stdio.h> 
#include <stdlib.h> 
#include <bzlib.h> 

/* gcc -o bz bz.c -lbz2 */ 

#define CHUNK 128 

struct bzdata { 
    FILE *fp; 
    BZFILE *bzf; 
    int bzeof, bzlen, bzpos; 
    char bzbuf[4096]; 
}; 

static int bz2_open(struct bzdata *bz, char *file); 
static void bz2_close(struct bzdata *bz); 
static int bz2_read_line(struct bzdata *bz, char **line, int *li); 
static int bz2_buf(struct bzdata *bz, char **line, int *li, int *ll); 


static int 
bz2_buf(struct bzdata *bz, char **line, int *li, int *ll) 
{ 
    int done = 0; 

    for (; bz->bzpos < bz->bzlen && done == 0; bz->bzpos++) { 
    if (*ll + 1 >= *li) { 
     *li += CHUNK; 
     *line = realloc(*line, (*li + 1) * sizeof(*(*line))); 
    } 
    if (((*line)[(*ll)++] = bz->bzbuf[bz->bzpos]) == '\n') { 
     done = 1; 
    } 
    } 

    if (bz->bzpos == bz->bzlen) { 
    bz->bzpos = bz->bzlen = 0; 
    } 

    (*line)[*ll] = '\0'; 

    return done; 
} 

static int 
bz2_read_line(struct bzdata *bz, char **line, int *li) 
{ 
    int bzerr = BZ_OK, done = 0, ll = 0; 

    if (bz->bzpos) { 
    done = bz2_buf(bz, line, li, &ll); 
    } 

    while (done == 0 && bz->bzeof == 0) { 
    bz->bzlen = BZ2_bzRead(&bzerr, bz->bzf, bz->bzbuf, sizeof(bz->bzbuf)); 

    if (bzerr == BZ_OK || bzerr == BZ_STREAM_END) { 
     bz->bzpos = 0; 

     if (bzerr == BZ_STREAM_END) { 
     bz->bzeof = 1; 
     } 
     done = bz2_buf(bz, line, li, &ll); 
    } else { 
     done = -1; 
    } 
    } 

    /* Handle last lines that don't have a line feed */ 
    if (done == 0 && ll > 0 && bz->bzeof) { 
    done = 1; 
    } 

    return done; 
} 

static int 
bz2_open(struct bzdata *bz, char *file) 
{ 
    int bzerr = BZ_OK; 

    if ((bz->fp = fopen(file, "rb")) && 
     (bz->bzf = BZ2_bzReadOpen(&bzerr, bz->fp, 0, 0, NULL, 0)) && 
     bzerr == BZ_OK) { 
    return 1; 
    } 

    return 0; 
} 

static void 
bz2_close(struct bzdata *bz) 
{ 
    int bzerr; 

    if (bz->bzf) { 
    BZ2_bzReadClose(&bzerr, bz->bzf); 
    bz->bzf = NULL; 
    } 

    if (bz->fp) { 
    fclose(bz->fp); 
    bz->fp = NULL; 
    } 
    bz->bzpos = bz->bzlen = bz->bzeof = 0; 
} 

int main(int argc, char *argv[]) { 
    struct bzdata *bz = NULL; 
    int i, lc, li = 0; 
    char *line = NULL; 

    if (argc < 2) { 
    return fprintf(stderr, "usage: %s file [file ...]\n", argv[0]); 
    } 

    if ((bz = calloc(1, sizeof(*bz)))) { 
    for (i = 1; i < argc; i++) { 
     if (bz2_open(bz, argv[i])) { 
     for (lc = 0; bz2_read_line(bz, &line, &li) > 0; lc++) { 
      /* Process line here */ 
     } 
     printf("%s: lines=%d\n", argv[i], lc); 
     } 
     bz2_close(bz); 
    } 

    free(bz); 
    } 

    if (line) { 
    free(line); 
    } 

    return 0; 
} 
Questions connexes