【问题标题】:C Program to Convert a Text File into a CSV File将文本文件转换为 CSV 文件的 C 程序
【发布时间】:2019-03-25 22:29:37
【问题描述】:

问题是使用 C 编程将文本文件转换为 CSV 文件。输入文本文件的格式如下: JACK Maria Stephan Nora 20 34 45 28 London NewYork Toronto Berlin

输出的 CSV 文件应如下所示:

Jack,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin

以下代码是我目前尝试过的:

void  load_and_convert(const char* filename){
    FILE *fp1, *fp2;
    char ch;

    fp1=fopen(filename,"r");
    fp2=fopen("output.csv","w");

    for(int i=0;i<1000;i++){
         ch=fgetc(fp1);
         fprintf(fp2,"%c",ch);    
         if(ch==' '|| ch=='\n')
              fprintf(fp2,"%c,\n",ch);
}
    fclose(fp1);
    fclose(fp2);

}

我的代码输出如下:

Jack,
Maria,
Stephan,
Nora,
20,
34,
45,
28,
London,
NewYork,
Toronto,
Berlin,

我应该如何修改我的代码以使其正常工作?

处理这个问题的想法是什么?

【问题讨论】:

  • Oh my... fgetc 返回一个 int 并且你会知道你已经读到文件末尾了,因为返回值是EOF.
  • 除此之外,您甚至还没有接近解决方案,因为您需要转置输出;要么您需要将内容读入二维数组,要么使用 3 FILE *s 或同样复杂的东西。
  • 如果真的这么小,可以用fgets阅读3行;并使用strtok_r 分别标记它们...
  • 有时,更高级的语言是解决方案...
  • OT:关于:fp1=fopen(filename,"r");fp2=fopen("output.csv","w"); 始终检查 (!=NULL) 返回值以确保操作成功。如果不成功,则调用perror( "my error message" );将您的错误消息和系统认为错误发生的文本原因输出到stderr

标签: c csv text


【解决方案1】:

由于我有一些时间,这里有一个适合您的解决方案(尽我所能使解决方案尽可能优雅):

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

#define MAX_STRING_LENGTH 50
#define MAX_NUMBER_OF_PEOPLE 50

typedef struct  
{  
  char name[MAX_STRING_LENGTH];
  int age;
  char city[MAX_STRING_LENGTH];
} Person;

void getName(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    strncpy(people[i].name, ptr, MAX_STRING_LENGTH);
    ptr = strtok(NULL, delim);
    i++;
  }
}

void getAge(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    people[i].age = atoi(ptr);
    i++;
    ptr = strtok(NULL, delim);
  }
}

void getCity(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    strncpy(people[i].city, ptr, MAX_STRING_LENGTH);
    i++;
    ptr = strtok(NULL, delim);
  }
}

int main(void)
{
  Person somebody[MAX_NUMBER_OF_PEOPLE];
  FILE *fp;
  char *line = NULL;
  size_t len = 0;
  ssize_t read;
  int ln = 0;

  fp = fopen("./test.txt", "r");
  if (fp == NULL)
      return -1;

  // Read every line, support first line is name, second line is age...
  while ((read = getline(&line, &len, fp)) != -1) {
    // remote trailing newline character
    line = strtok(line, "\n");
    if (ln == 0) {
      getName(line, " ", somebody);
    } else if (ln == 1) {
      getAge(line, " ", somebody);
    } else {
      getCity(line, " ", somebody);
    }
    ln++;
  }

  for (int j = 0; j < MAX_NUMBER_OF_PEOPLE; j++) {
      if (somebody[j].age == 0) 
        break;
      printf("%s, %d, %s\n", somebody[j].name, somebody[j].age, somebody[j].city);
  }

  fclose(fp);
  if (line)
      free(line);

  return 0;
}

【讨论】:

    【解决方案2】:

    如果您想解决在将每行中具有 4 个字段的 3 行转换为具有 3-每行字段。因此,当您的数据文件包含:

    输入文件示例

    $ cat dat/col2csv3x4.txt
    JACK Maria Stephan Nora
    20 34 45 28
    London NewYork Toronto Berlin
    

    您想读取三行中的每一行,然后将列转换为行以供.csv 输出。这意味着您最终将得到 4 行 3-csv 字段,例如

    预期程序输出

    $ ./bin/transpose2csv < dat/col2csv3x4.txt
    JACK,20,London
    Maria,34,NewYork
    Stephan,45,Toronto
    Nora,28,Berlin
    

    做起来没有什么难的,但是在处理对象的内存存储和分配/重新分配来处理从3行4块数据到4行3块数据的转换需要一丝不苟数据。

    一种方法是将所有原始行读入典型的指向字符的指针设置。然后将列转换/转置为行。由于可以想象下一次可能会有 100 行和 500 个字段,因此您将需要使用索引和计数器来进行转换,以跟踪您的分配和重新分配要求,以使您完成的代码能够处理将通用数量的行和字段转换为字段 - 每行具有与原始行一样多的 vales 的行数。

    您可以设计代码以提供两种基本功能的转换。第一个读取和存储行 (saygetlines`),第二个将这些行转换为新的指向 char 的指针,以便它可以输出为 逗号分隔值

    处理这两个函数的一种方法类似于以下将要读取的文件名作为第一个参数(如果没有给出参数,则默认从stdin 读取)。代码并不简单,但也不难。只需跟踪所有分配,保留指向每个分配开头的指针,以便在不再需要时释放内存,例如

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define NPTR 2
    #define NWRD 128
    #define MAXC 1024
    
    /** getlines allocates all storage required to read all lines from file.
     *  the pointers are doubled each time reallocation is needed and then
     *  realloc'ed a final time to exactly size to the number of lines. all
     *  lines are stored with the exact memory required.
     */
    char **getlines (size_t *n, FILE *fp)
    {
        size_t nptr = NPTR;     /* tracks number of allocated pointers */
        char buf[MAXC];         /* tmp buffer sufficient to hold each line */
        char **lines = calloc (nptr, sizeof *lines);
    
        if (!lines) {   /* validate EVERY allocaiton */
            perror ("calloc-lines");
            return NULL;
        }
    
        *n = 0;         /* pointer tracks no. of lines read */
        rewind (fp);    /* clears stream error state if set */
    
        while (fgets (buf, MAXC, fp)) { /* read each line o finput */
            size_t len;
    
            if (*n == nptr) {   /* check/realloc ptrs if required */
                void *tmp = realloc (lines, 2 * nptr * sizeof *lines);
                if (!tmp) {     /* validate reallocation */
                    perror ("realloc-tmp");
                    break;
                }
                lines = tmp;    /* assign new block, (opt, zero new mem below) */
                memset (lines + nptr, 0, nptr * sizeof *lines);
                nptr *= 2;      /* increment allocated pointer count */
            }
    
            buf[(len = strcspn(buf, "\r\n"))] = 0;  /* get line, remove '\n' */
            lines[*n] = malloc (len + 1);           /* allocate for line */
            if (!lines[*n]) {                       /* validate */
                perror ("malloc-lines[*n]");
                break;
            }
            memcpy (lines[(*n)++], buf, len + 1);   /* copy to line[*n] */
        }
    
        if (!*n) {          /* if no lines read */
            free (lines);   /* free pointers */
            return NULL;
        }
    
        /* optional final realloc to free unused pointers */
        void *tmp = realloc (lines, *n * sizeof *lines);
        if (!tmp) {
            perror ("final-realloc");
            return lines;
        }
    
        return (lines = tmp);   /* return ptr to exact no. of required ptrs */
    }
    
    /** free all pointers and n alocated arrays */
    void freep2p (void *p2p, size_t n)
    {
        for (size_t i = 0; i < n; i++)
            free (((char **)p2p)[i]);
        free (p2p);
    }
    
    /** transpose a file of n rows and a varying number of fields to an
     *  allocated pointer-to-pointer t0 char structure with a fields number 
     *  of rows and n csv values per row.
     */
    char **transpose2csv (size_t *n, FILE *fp)
    {
        char **l = NULL, **t = NULL;
        size_t  csvl = 0,       /* csv line count */
                ncsv = 0,       /* number of csv lines allocated */
                nchr = MAXC,    /* initial chars alloc for csv line */
                *offset,        /* array tracking read offsets in lines */
                *used;          /* array tracking write offset to csv lines */
    
        if (!(l = getlines (n, fp))) {  /* read all lines to l */
            fputs ("error: getlines failed.\n", stderr);
            return NULL;
        }
        ncsv = *n;
    #ifdef DEBUG
        for (size_t i = 0; i < *n; i++)
            puts (l[i]);
    #endif
    
        if (!(t = malloc (ncsv * sizeof *t))) { /* alloc ncsv ptrs for csv */
            perror ("malloc-t");
            freep2p (l, *n);        /* free everything else on failure */
            return NULL;
        }
    
        for (size_t i = 0; i < ncsv; i++)   /* alloc MAXC chars to csv ptrs */
            if (!(t[i] = malloc (nchr * sizeof *t[i]))) {
                perror ("malloc-t[i]");
                while (i--)         /* free everything else on failure */
                    free (t[i]);
                free (t);
                freep2p (l, *n);
                return NULL;
            }
    
        if (!(offset = calloc (*n, sizeof *offset))) {  /* alloc offsets array */
            perror ("calloc-offsets");
            free (t);
            freep2p (l, *n);
            return NULL;
        }
    
        if (!(used = calloc (ncsv, sizeof *used))) {    /* alloc used array */
            perror ("calloc-used");
            free (t);
            free (offset);
            freep2p (l, *n);
            return NULL;
        }
    
        for (;;) {  /* loop continually transposing cols to csv rows */
            for (size_t i = 0; i < *n; i++) { /* read next word from each line */
                char word[NWRD];    /* tmp buffer for word */
                int off;            /* number of characters consumed in read */
                if (sscanf (l[i] + offset[i], "%s%n", word, &off) != 1)
                    goto readdone;  /* break nested loops on read failure */
                size_t len = strlen (word);         /* get word length */
                offset[i] += off;                   /* increment read offset */
                if (csvl == ncsv) { /* check/realloc new csv row as required */
                    size_t newsz = ncsv + 1;    /* allocate +1 row over *n */
                    void *tmp = realloc (t, newsz * sizeof *t); /* realloc ptrs */
                    if (!tmp) {
                        perror ("realloc-t");
                        freep2p (t, ncsv);
                        goto readdone;
                    }
                    t = tmp;
                    t[ncsv] = NULL;     /* set new pointer NULL */
    
                    /* allocate nchr chars to new pointer */
                    if (!(t[ncsv] = malloc (nchr * sizeof *t[ncsv]))) {
                        perror ("malloc-t[i]");
                        while (ncsv--)   /* free everything else on failure */
                            free (t[ncsv]);
                        goto readdone;
                    }
    
                    tmp = realloc (used, newsz * sizeof *used); /* realloc used */
                    if (!tmp) {
                        perror ("realloc-used");
                        freep2p (t, ncsv);
                        goto readdone;
                    }
                    used = tmp;
                    used[ncsv] = 0;
    
                    ncsv++;
                }
                if (nchr - used[csvl] - 2 < len) {  /* check word fits in line */
                    /* realloc t[i] if required (left for you) */
                    fputs ("realloc t[i] required.\n", stderr);
                }
                /* write word to csv line at end */
                sprintf (t[csvl] + used[csvl], used[csvl] ? ",%s" : "%s", word);
                t[csvl][used[csvl] ? used[csvl] + len + 1 : len] = 0;
                used[csvl] += used[csvl] ? len + 1 : len;
            }
            csvl++;
        }
        readdone:;
    
        freep2p (l, *n);
        free (offset);
        free (used);
    
        *n = csvl;
    
        return t;
    }
    
    int main (int argc, char **argv) {
    
        char **t;
        size_t n = 0;
        /* use filename provided as 1st argument (stdin by default) */
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {  /* validate file open for reading */
            perror ("file open failed");
            return 1;
        }
    
        if (!(t = transpose2csv (&n, fp))) {
            fputs ("error: transpose2csv failed.\n", stderr);
            return 1;
        }
    
        if (fp != stdin) fclose (fp);   /* close file if not stdin */
    
        for (size_t i = 0; i < n; i++)
            if (t[i])
            puts (t[i]);
    
        freep2p (t, n);
    
        return 0;
    }
    

    使用/输出示例

    $ ./bin/transpose2csv < dat/col2csv3x4.txt
    JACK,20,London
    Maria,34,NewYork
    Stephan,45,Toronto
    Nora,28,Berlin
    

    内存使用/错误检查

    在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

    您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

    对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

    $ valgrind ./bin/transpose2csv < dat/col2csv3x4.txt
    ==18604== Memcheck, a memory error detector
    ==18604== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==18604== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
    ==18604== Command: ./bin/transpose2csv
    ==18604==
    JACK,20,London
    Maria,34,NewYork
    Stephan,45,Toronto
    Nora,28,Berlin
    ==18604==
    ==18604== HEAP SUMMARY:
    ==18604==     in use at exit: 0 bytes in 0 blocks
    ==18604==   total heap usage: 15 allocs, 15 frees, 4,371 bytes allocated
    ==18604==
    ==18604== All heap blocks were freed -- no leaks are possible
    ==18604==
    ==18604== For counts of detected and suppressed errors, rerun with: -v
    ==18604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    始终确认您已释放已分配的所有内存并且没有内存错误。

    查看一下,如果您还有其他问题,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-21
      • 2013-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-17
      相关资源
      最近更新 更多