【问题标题】:Reading in a multi-line string from a file using sscanf in C在 C 中使用 sscanf 从文件中读取多行字符串
【发布时间】:2015-07-26 01:04:51
【问题描述】:

我有一个包含特定记录信息的文本文件,文件格式如下:

Title: Void
Artist: RL Grime
Year: 2014
Genre: Bass
Label: We Did It
Price: 14.95
----
Title: Mssingno EP
Artist: Mssingno
Year: 2013
Genre: Grime / Garage
Label: Goon Club Allstars
Price: 10.00
----

我正在尝试编写一个程序,该程序可以使用sscanf() 解析其中的数据,然后将每个集合存储到一个名为Recordstruct 中。 Record 看起来像这样:

typedef struct
{
    char    title[80];
    char    artist[80];
    int     year;
    char    genre[80];
    char    label[80];
    double  price;
} Record;

Record record_inventory[MAX_RECORDS];

但我想知道我能做些什么来真正解析这些数据。我可以写一行,但我不知道如何用sscanf() 写一个完整的段落。

例如,单行(我从 ifp 读取的文件)如下所示:

char input[1024];
while(fgets(input, 1024, ifp))
{
    sscanf(input, "Title: %[^,]", record_inventory[0].title);
}

但很明显,由于输入只能包含一行,因此我无法以任何方式添加到 sscanf。除了在fgetssscanf 之间交替四次之外,还有什么有效的方法可以用 fgets 获取四行然后解析它?

【问题讨论】:

  • 如果您可以将段落转换为单个字符串,sscanf() 将对其进行处理。我不认为我会建议这样做,但它可以做到。一次读取一行并依次处理每一行有什么问题?无论如何,如果您想连续七次拨打fgets(),您可以这样做。您甚至可以让它将额外的行添加到单个缓冲区。您可以一直这样做,直到遇到带有 ---- 的行和换行符,或者您可以进行计数 - 无论您喜欢哪个。
  • 第二个乔纳森,上面。 fscanf 确实是不适合这项工作的工具,但如果您决心这样做,请在同一函数调用中指明 所有 要读取的变量(例如 fscanf(input, "Title: %s\nArtist:%s\nYear: %d\n", Record.title, Record.artist, Record.year))跨度>
  • @Curt 哪种工具更适合这项工作?我真的只了解了sscanf。有更好的吗?
  • @Alex 我会或多或少地按照乔纳森的建议做,并在循环中逐行读取记录,直到您到达文件末尾。除非您对文件数据的质量完全有信心,否则我会将记录读入中间变量,以便您可以处理非数字年份或超过 80 个字符的标题等。

标签: c string parsing


【解决方案1】:
#include <stdio.h>
#include <string.h>

#define MAX_RECORDS 64

typedef struct {
    char    title[80];
    char    artist[80];
    int     year;
    char    genre[80];
    char    label[80];
    double  price;
} Record;

Record record_inventory[MAX_RECORDS];

int main(void){
    char input[128], rec[6*128] = "";
    int n = 0;
    FILE *ifp = fopen("data.txt", "r");


    while(fgets(input, sizeof input, ifp)){
        if(strncmp(input, "----", 4)==0){
            if(6!=sscanf(rec ,
                "Title: %79[^\n] "
                "Artist: %79[^\n] "
                "Year: %d "
                "Genre: %79[^\n] "
                "Label: %79[^\n] "
                "Price: %lf",
                record_inventory[n].title,
                record_inventory[n].artist,
                &record_inventory[n].year,
                record_inventory[n].genre,
                record_inventory[n].label,
                &record_inventory[n].price)){
                fprintf(stderr, "bad format!\n");
            } else {
                if(++n == MAX_RECORDS){
                    fprintf(stderr, "full.\n");
                    break;
                }
            }
            *rec = '\0';
        } else {
            strcat(rec, input);//strcat up to ----
        }
    }
    fclose(ifp);
    //check print
    for(int i = 0; i < n; ++i){
        printf("%s : %f\n", record_inventory[i].title, record_inventory[i].price);
    }
    return 0;
}

【讨论】:

    【解决方案2】:

    您显示的数据很好且一致 - 6 个数据行和一个记录结束 (EOR) 标记,字段顺序相同。目前尚不清楚是否可以安全地假设所有数据都将受到严格的约束。让我们假设,pro tem,它是。然后您需要读取并累积行到 EOR,然后处理结果数据。

    您也没有规定您是否在支持 POSIX getline() 的平台上。我会假设你是因为它让生活更简单。如有必要,您可以使用fgets() 进行操作。

    您可以使用以下代码读取 EOR:

    static size_t max(size_t x, size_t y) { return (x > y) ? x : y; }
    
    char *get_record(FILE *fp, const char *eor)
    {
        char *ibuffer = 0;
        size_t ibuflen = 0;
        char *obuffer = 0;
        size_t obuflen = 0;
        size_t omaxlen = 0;
        ssize_t ilen;
        size_t eorlen = strlen(eor);
    
        while ((ilen = getline(&ibuffer, &ibuflen, fp)) != -1)
        {
            if (obuflen + ilen + 1 >= omaxlen)
            {
                size_t nbuflen = max(obuflen * 2, obuflen + ilen + 1);
                void *nbuffer = realloc(obuffer, nbuflen);
                if (nbuffer == 0)
                {
                    free(ibuffer);
                    free(obuffer);
                    return 0;
                }
                obuffer = nbuffer;
                omaxlen = nbuflen;
            }
            memmove(obuffer + obuflen, ibuffer, ilen + 1);
            obuflen += ilen;
            if (strncmp(ibuffer, eor, eorlen) == 0 && ibuffer[eorlen] == '\n')
                break;
        }
        free(ibuffer);
        return obuffer;
    }
    
    /* Test harness for get_record() */
    int main(void)
    {
        char *buffer;
    
        while ((buffer = get_record(stdin, "----")) != 0)
        {
            printf("[[%s]]\n", buffer);
            free(buffer);
        }
    
        return 0;
    }
    

    源文件getrec.c

    然后可以扩展以处理来自记录的结构,如下所示:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    enum { MAX_RECORDS = 20 };
    enum { MAX_TAG = 20 };
    
    typedef struct
    {
        char    title[80];
        char    artist[80];
        int     year;
        char    genre[80];
        char    label[80];
        double  price;
    } Record;
    
    static Record record_inventory[MAX_RECORDS];
    static size_t n_rec = 0;
    
    static size_t max(size_t x, size_t y) { return (x > y) ? x : y; }
    
    extern char *get_record(FILE *fp, const char *eor);
    extern int scan_record(const char *buffer, Record *record);
    extern void print_record(size_t i, const Record *record);
    
    char *get_record(FILE *fp, const char *eor)
    {
        char *ibuffer = 0;
        size_t ibuflen = 0;
        char *obuffer = 0;
        size_t obuflen = 0;
        size_t omaxlen = 0;
        ssize_t ilen;
        size_t eorlen = strlen(eor);
    
        while ((ilen = getline(&ibuffer, &ibuflen, fp)) != -1)
        {
            if (obuflen + ilen + 1 >= omaxlen)
            {
                size_t nbuflen = max(obuflen * 2, obuflen + ilen + 1);
                void *nbuffer = realloc(obuffer, nbuflen);
                if (nbuffer == 0)
                {
                    free(ibuffer);
                    free(obuffer);
                    return 0;
                }
                obuffer = nbuffer;
                omaxlen = nbuflen;
            }
            memmove(obuffer + obuflen, ibuffer, ilen + 1);
            obuflen += ilen;
            if (strncmp(ibuffer, eor, eorlen) == 0 && ibuffer[eorlen] == '\n')
                break;
        }
        free(ibuffer);
        return obuffer;
    }
    
    static int scan_tag(const char *tag, const char *data)
    {
        int pos;
        char fmtstr[MAX_TAG];
        sprintf(fmtstr, " %%%d[^:]:%%n", MAX_TAG - 1);
        char tagstr[MAX_TAG];
        if (sscanf(data, fmtstr, tagstr, &pos) != 1)
            return 0;
        if (strcmp(tagstr, tag) != 0)
            return 0;
        return pos + 1;
    }
    
    static size_t scan_string(const char *tag, const char *data, char *buffer, size_t buflen)
    {
        int pos1 = scan_tag(tag, data);
        if (pos1 == 0)
            return 0;
    
        char fmtstr[MAX_TAG];
        int pos2;
        sprintf(fmtstr, " %%%zu[^\n]%%n", buflen - 1);
        if (sscanf(data + pos1, fmtstr, buffer, &pos2) != 1)
            return 0;
        return (size_t)(pos1 + pos2);
    }
    
    static size_t scan_integer(const char *tag, const char *data, int *int_val)
    {
        int pos1 = scan_tag(tag, data);
        if (pos1 == 0)
            return 0;
    
        int pos2;
        if (sscanf(data + pos1, "%d%n", int_val, &pos2) != 1)
            return 0;
        return (size_t)(pos1 + pos2);
    }
    
    static size_t scan_double(const char *tag, const char *data, double *dbl_val)
    {
        int pos1 = scan_tag(tag, data);
        if (pos1 == 0)
            return 0;
    
        int pos2;
        if (sscanf(data + pos1, "%lf%n", dbl_val, &pos2) != 1)
            return 0;
        return (size_t)(pos1 + pos2);
    }
    
    int scan_record(const char *buffer, Record *record)
    {
        size_t offset = 0;
        const char *scan_pos = buffer + offset;
    
        if ((offset = scan_string("Title", scan_pos, record->title, sizeof(record->title))) == 0)
            return -1;
        scan_pos += offset;
        if ((offset = scan_string("Artist", scan_pos, record->artist, sizeof(record->artist))) == 0)
            return -1;
        scan_pos += offset;
        if ((offset = scan_integer("Year", scan_pos, &record->year)) == 0)
            return -1;
        scan_pos += offset;
        if ((offset = scan_string("Genre", scan_pos, record->genre, sizeof(record->genre))) == 0)
            return -1;
        scan_pos += offset;
        if ((offset = scan_string("Label", scan_pos, record->label, sizeof(record->label))) == 0)
            return -1;
        scan_pos += offset;
        if ((offset = scan_double("Price", scan_pos, &record->price)) == 0)
            return -1;
        return 0;
    }
    
    void print_record(size_t i, const Record *record)
    {
        printf("Record:  %zu\n", i);
        printf("Title:   %s\n", record->title);
        printf("Artist:  %s\n", record->artist);
        printf("Year:    %4d\n", record->year);
        printf("Genre:   %s\n", record->genre);
        printf("Label:   %s\n", record->label);
        printf("Price:   %.2f\n", record->price);
        putchar('\n');
    }
    
    int main(void)
    {
        char *buffer;
        int rc = 0;
    
        while ((buffer = get_record(stdin, "----")) != 0 && rc == 0)
        {
            printf("Input %zu: [[%s]]\n", n_rec, buffer);
            rc = scan_record(buffer, &record_inventory[n_rec++]);
            free(buffer);
        }
    
        for (size_t i = 0; i < n_rec; i++)
            print_record(i, &record_inventory[i]);
    
        return 0;
    }
    

    示例输入文件data

    Title: Void
    Artist: RL Grime
    Year: 2014
    Genre: Bass
    Label: We Did It
    Price: 14.95
    ----
    Title: Mssingno EP
    Artist: Mssingno
    Year: 2013
    Genre: Grime / Garage
    Label: Goon Club Allstars
    Price: 10.00
    ----
    

    样本输出

    $ ./getrec < data
    Input 0: [[Title: Void
    Artist: RL Grime
    Year: 2014
    Genre: Bass
    Label: We Did It
    Price: 14.95
    ----
    ]]
    Input 1: [[Title: Mssingno EP
    Artist: Mssingno
    Year: 2013
    Genre: Grime / Garage
    Label: Goon Club Allstars
    Price: 10.00
    ----
    ]]
    Record:  0
    Title:   Void
    Artist:  RL Grime
    Year:    2014
    Genre:   Bass
    Label:   We Did It
    Price:   14.95
    
    Record:  1
    Title:   Mssingno EP
    Artist:  Mssingno
    Year:    2013
    Genre:   Grime / Garage
    Label:   Goon Club Allstars
    Price:   10.00
    
    $
    

    处理记录元素的可变顺序、缺失元素或额外元素,留作练习。 scan_tag() 函数需要返回它找到的标签,而不是检查它是否被赋予了正确的标签。调用代码必须适应已找到的内容,如果同一标签出现多次则报告错误等。

    显示的代码不能很好地报告错误。它发现它们没问题,但它没有报告问题所在。有很多方法可以做到这一点;你必须决定什么是最适合你的。

    在提交之前,请确保您了解整个代码中发生的情况;其中一些不是初学者写的那种东西。

    【讨论】:

      最近更新 更多