代码之家  ›  专栏  ›  技术社区  ›  Akra

C从文本文件复制子字符串

  •  1
  • Akra  · 技术社区  · 7 年前

    假设我有以下文本文件-

    name:asdfg
    address:zcvxz
    ,
    name:qwerwer
    address:zxcvzxcvxz
    ,
    

    我想将名称(不带“name:”)复制到某个字符串变量,将地址复制到另一个字符串变量,依此类推。

    如何在不损坏内存的情况下做到这一点? 尝试使用(示例)-

    char buf[50];
    while (fgets(buf, 50, file) != NULL) {
            if (!strncmp(buf, "name", 4)) 
                strncpy(somestring, buf + 5, 20)
            //do the same for address, continue looping
    

    但是文本行的长度不同,所以它似乎从缓冲区复制了各种垃圾,因为字符串不是以null结尾的,所以它复制“ asdfg公司 废话废话”。

    2 回复  |  直到 7 年前
        1
  •  0
  •   David C. Rankin    7 年前

    您使用 fgets 处理文件I/O,因为它提供了一种更灵活、更健壮的方式来读取、验证和准备解析所读取的数据行。通常建议这样做 面向生产线 输入(来自文件或用户)。但是,这是将多个记录视为 已格式化 输入确实有一些优势。

    让我们从读取数据文件并捕获 name:.... address:... 简单数据结构中的数据,用于在 20-char 每个的数组。读取每一行,验证长度,尾部 '\n' 已删除,然后 strchr 用于定位 ':' 排队等候。(我们不关心没有 ':' ). 之前的标签 ':' 复制到 tmp 然后与 "name" "address" 确定要读取的值。一旦读取地址数据,两个 name addr 值打印到 stdout ,

    #include <stdio.h>
    #include <string.h>
    
    enum { MAXC = 20, MAXS = 256 };
    
    typedef struct {
        char name[MAXC],
            addr[MAXC];
    } data;
    
    int main (int argc, char **argv) {
    
        char buf[MAXS] = "",
            *name = "name",     /* name/address literals for comparison */
            *addr = "address";
        data mydata = { .name = "" };
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {  /* validate file open for reading */
            fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
            return 1;
        }
    
        while (fgets (buf, MAXS, fp)) {         /* read each line */
            char *p = buf,                      /* pointer to use with strchr */
                tmp[MAXC] = "";                 /* storage for labels */
            size_t len = strlen (buf);          /* get buf len */
            if (len && buf[len - 1] == '\n')    /* validate last char is '\n' */
                buf[--len] = 0;                 /* overwrite with nul-character */
            else if (len + 1 == MAXS) {         /* handle string too long */
                fprintf (stderr, "error: line too long or no '\n'\n");
                return 1;
            }
            if ((p = strchr (buf, ':'))) {      /* find ':' in buf */
                size_t labellen = p - buf,      /* get length of label */
                    datalen = strlen (p + 1);   /* get length of data */
                if (labellen + 1 > MAXC) {  /* validate both lengths */
                    fprintf (stderr, "error: label exceeds '%d' chars.\n", MAXC);
                    return 1;
                }
                if (datalen + 1 > MAXC) {
                    fprintf (stderr, "error: data exceeds '%d' chars.\n", MAXC);
                    return 1;
                }
                strncpy (tmp, buf, labellen);   /* copy label to temp */
                tmp[labellen] = 0;              /* nul-terminate */
                if (strcmp (name, tmp) == 0)        /* is the label "name" ? */
                    strcpy (mydata.name, p + 1);
                else if (strcmp (addr, tmp) == 0) { /* is the label "address" ? */
                    strcpy (mydata.addr, p + 1);
                    /* record complete -- output results */
                    printf ("\nname : %s\naddr : %s\n", mydata.name, mydata.addr);
                }
            }
        }
    
        if (fp != stdin) fclose (fp);   /* close file if not stdin */
    
        return 0;
    }
    

    ( 注: 有很多方法可以构建这种逻辑。上述示例仅代表一种半标准方法)

    使用/输出示例

    $./bin/nameaddr <dat/nameaddr.txt
    
    name : asdfg
    addr : zcvxz
    
    name : qwerwer
    addr : zxcvzxcvxz
    

    我很难让你相信 fgets 是解决这个问题的方法。为什么?这里我们基本上是在阅读 已格式化 由3行数据组成的输入。这个 设置字符串格式 对于 fscanf 不在乎涉及多少行,可以轻松构造为跳过 “\n” 在格式化输入中。这可以为正确的输入文件提供(一个更脆弱但有吸引力的选择)。

    例如,上面的代码可以使用 fscanf公司 对于格式化读取:

    #include <stdio.h>
    
    #define MAXC 20
    
    typedef struct {
        char name[MAXC],
            addr[MAXC];
    } data;
    
    int main (int argc, char **argv) {
    
        data mydata = { .name = "" };
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {  /* validate file open for reading */
            fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
            return 1;
        }
    
        /* read 3-lines at a time separating name and address at once */
        while (fscanf (fp, " name:%19s address:%19s ,", 
                mydata.name, mydata.addr) == 2)
            printf ("\nname : %s\naddr : %s\n", mydata.name, mydata.addr);
    
        if (fp != stdin) fclose (fp);   /* close file if not stdin */
    
        return 0;
    }
    

    (输出相同)

    在极少数情况下,对于正确的数据文件, fscanf公司 可以为 面向生产线 阅读时使用 fgets . 然而,您的第一选择应该仍然是 使用以下方法之一 fgets 或POSIX getline .

    如果你还有其他问题,请仔细查看并告诉我。

        2
  •  0
  •   Barmar 0___________    7 年前

    如果名称为20个字符或更长, strncpy() 不会将空终止符复制到目标字符串,因此您需要自己添加它。

    strncpy(somestring, buf + 5, 19);
    somestring[19] = '\0';