从包含整数的文件中读取一行

时间:2019-08-03 18:28:40

标签: c

我需要从数据文件中读取以下内容:

  

Sabre Corporation新墨西哥州新科里奥西Henness Lane 15790号65790

我的变量是char companyName[20+1]char companyAddress[30+1]char companyCity[15+1]char companyState[15+1]int companyZip

我使用companyName读了%[^\n]的第一行,但是试图以相同的方式读取第二行,变量保持为空。

void getCompanyData(FILE *CompanyFile, char *companyName, char *companyAddress, char *companyCity, char *companyState, int *companyZip)
{
    fscanf(CompanyFile,"%[^\n]%[^\n]%s%s%d", companyName, companyAddress, companyCity, companyState, companyZip);
}

当我运行此代码并打印出变量时,companyName看起来很好,“ Sabre Corporation”,但是companyAddress却没有显示为任何内容。

如果我将第二个输入切换为简单的%s,它将读取地址的数字。

是否有办法像整行一样将整行读取为第一行,而不是将其他许多变量连接成一个更大的变量?

3 个答案:

答案 0 :(得分:4)

C的一点翻译。

"%[^\n]%[^\n]%s%s%d"

%[^\n]        // reads everything up to the new line
              // But does not read the new line character.
              // So there is still a new  line character on the stream.

%[^\n]%[^\n]  // So the first one reads up to the new line.
              // The second one will immediately fail as there is a new line
              // still on the stream and thus not read anything. 

所以:

int count = scanf(CompanyFile,"%[^\n]%[^\n]%s%s%d", /*Variables*/ );
printf("Count = %d\n", count);

由于仅填充了一个变量,因此将打印1。

我知道使用以下内容来读一行很诱人。

 fscanf("%[^\n]\n", /* Variables*/ );

但这是一个坏主意,因为很难发现空行。空行不会将任何内容读入变量,因此在读取新行之前会失败,因此它实际上不会读取空行。因此最好将其分解为多个语句。

 int count;
 do {
     count = fscanf("%[^\n]", /* Variables*/ );
     fscanf("\n");
 } while (count == 0);
 // successfully read the company name and moved on to next line
 // while skipping completely empty lines.

现在这似乎是上述内容的逻辑扩展。
但这不是最好的方法。如果您假设一行可能以上一行的'\ n'开头(并且您想忽略数据行上的任何前导空白),则可以在前面使用一个空格。

 int count = fscanf(" %[^\n]", /* Variables*/ );
                  // ^ The leading space will drop all white space characters.
                  // this includes new lines so if you expect the last read may
                  // have left the new line on the stream this will drop it.

要注意的另一件事是,您应始终检查fscanf()的返回值,以确保实际扫描了预期已扫描的变量数。

答案 1 :(得分:2)

  

是否有办法像整行一样将整行读取为第一行,而不是将其他许多变量连接成一个更大的变量?

如果目标是读取(所有字符包括'\n',包括fgets())。

buffer[256];
if (fgets(buffer, sizeof buffer, CompanyFile) {
  // success!
}

读入 string 后,对其进行解析。


“如果没有其他规则,“ Sabre Corporation 15790 West Henness Lane New Corio,新墨西哥州65790”显然无法解析为companyName[], companyAddress[], companyCity[], companyState[], companyZip

我希望会有更多的逗号。

考虑

3M 255 Century Ave N Maplewood,MN 55119(公司名称和数字)
西南航空P.O. Box 36647 Dallas,德克萨斯州75235(无街道编号,邮政信箱)

答案 2 :(得分:1)

您遇到的最大问题是确定街道地址的终止位置和城市的起始位置:

Sabre Corporation 15790 West Henness Lane New Corio, New Mexico 65790

正如@chux所提到的,您可能希望看到其他逗号或其他分隔符,以将公司名称与街道地址分开,并将街道地址与城市名称分开。但是,所有信息并没有丢失,而是需要假设街道地址的末尾带有可识别的单词,例如"boulevard", "drive", "lane", "street", etc...(以及缩写,例如"Blvd.", "Dr.", etc..,您可以根据需要添加)然后可以创建一个简单的查询,将字符串中的各个单词进行比较,以识别街道地址的结尾和城市的起点。

在C语言中,只要有遵循的规则就可以解析任何内容,这些规则使您可以从较大的文本中找到想要的内容的开头和结尾。在此出于示例目的,我们将假定公司名称不包含数字(您可以添加代码以处理@chux答案中建议的名称)

有很多方法可以从输入行中解析所需的信息。对于初学者,您要将其存储在哪里?每当您需要将不同的信息作为一个对象进行协调时,您都应该考虑struct(如果您有多个对象,它就会适合于结构数组)。因此,在您的情况下,我们可以定义类似于以下内容的结构(实际上是结构的typedef

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */
#define ADDR   30
#define NAME   20
#define COMP   15

typedef struct {        /* struct to hold address parts */
    char name[NAME+1],
        addr[ADDR+1],
        city[COMP+1],
        state[COMP+1];
        int zip;
} co_t;

正如我的评论中所讨论的,一个好的方法是使用fgets将整行读入缓冲区,从而可以独立验证(1)读取本身,以及(2)从缓冲区解析项目。此外,当您一次读取整行时,整行都被消耗了,为下一次读取准备了输入缓冲区,而您不必担心fscanf matching input 失败,从而在输入缓冲区中留下了部分未读的行。这样,您可以使用以下命令将行读入缓冲区:

int main (int argc, char **argv) {

    char buf[MAXC], *p = buf, *ep = buf, tmp[MAXC];
    co_t company = { .name = "" };
    /* 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 (!fgets (buf, MAXC, fp)) {   /* read entire line into buf */
        fputs ("(user canceled or stream error)\n", stderr);
        return 1;
    }
    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

注意:,在上面声明了缓冲区buf,然后是两个指向缓冲区的指针,分别将起始指针p和结束指针ep声明为帮助解析,以及另一个临时缓冲区tmp也有帮助。您声明结构的实例(初始化为全零),并在实际从文件中读取一行之前验证您已打开文件以供读取)

要将您的公司名称与其他名称分开,我们可以轻松地在缓冲区中找到第一个数字(假设您的公司名称不包含数字,则第一个数字将是街道地址的开头),然后进行备份以找到名称中的最后一个字母字符(将+1向前移动到其后的第一个空格,因此我们将名称放在buf的开头和指针之间)。然后只需将名称复制到company.name nul-terminate

由于地址开头的数字可以是任何数字,因此您可以使用isdigit()从头开始检查,也可以使用非常方便的strpbrk()函数来查找地址的第一个数字您返回指向它的指针,例如

    if ((p = strpbrk (p, "0123456789"))) {      /* locate 1st digit in buf */
        ep = p;                                 /* set endptr to pointer */
        while (ep > buf && !isalpha (*ep))      /* backup to find name end */
            ep--;
        ep++;                                   /* adv. to space after name */
        if (ep - buf <= NAME) {                 /* if name will fit */
            memcpy (company.name, buf, ep - buf);   /* copy to company.name */
            company.name[ep - buf + 1] = 0;         /* nul-terminate */
        }
    }
    else {  /* no number found, invalid input */
        fputs ("error: street number not found.\n", stderr);
        return 1;
    }

注意:,上面我们保存了p,使其继续指向地址的开头,然后使用ep进行备份以查找名称的结尾(它们之间可能有多个空格,等等。)然后将名称放在bufep之间)

将想要的文本放在方括号中之后,指针(ep - buf)的不同为您提供了需要复制到company.name的字符串的字符数(长度),因此您只需使用memcpy,因为名称末尾没有 nul-termination 字符,从而消除了使用strcpy作为可能的解决方案。无论如何,没有理由使用strcpy,因为您已经知道要复制多少个字符,因此以下就是您所需要的:

            memcpy (company.name, buf, ep - buf);   /* copy to company.name */
            company.name[ep - buf + 1] = 0;         /* nul-terminate */

回想一下,p指向街道地址的开头,但是在这里我们遇到了一个问题:地址在哪里停止,城市在哪里开始?字符串中下一个可识别的里程碑将是城市之后的','。因此,让我们使用strchr来查找逗号,将端点指针ep设置为指向逗号,然后将整个地址和城市复制到临时缓冲区tmp中以进行进一步处理。 / p>

我们需要一个临时缓冲区,因为我们将使用strtok将临时缓冲区拆分为令牌(单词),称为“令牌化”字符串。之所以需要临时缓冲区,是因为strtok在解析字符串中的单词时,通过用 nul-character 替换每个分隔符序列来修改其作用的缓冲区。这里的想法是将tmp缓冲区分成令牌,并逐字检查街道的结尾,例如"boulevard", "drive", "lane", "street", etc...查找地址的末尾。 (注意:为了简化比较,所有内容均以小写字母列出)。

因此,我们要做的就是获取每个令牌,将其转换为小写字母,然后与每个街道的结尾进行比较以找到地址的结尾。如果令牌是结束符,则返回1的简短函数;如果不是,则返回0的简短函数,例如

/* simple function to look for word that is street ending */
int streetending (const char *s)
{
    char buf[MAXC], *p = buf;   /* temporary buf, convert s to lowercase */
    char *endings[] = { "boulevard", "drive", "lane", "street", NULL },
        **e = endings;  /* pointer to endings */

    strcpy (buf, s);    /* copy s to buf */

    while (*p) {        /* convert buf to all lowercase (for comparison) */
        *p = tolower (*p);
        p++;
    }

    while (*e) {        /* loop over street endings compare to buf */
        if (strcmp (buf, *e) == 0)  /* if match, return success */
            return 1;
        e++;            /* advance pointer to next ending */
    }

    return 0;   /* s not street ending, return failure */
}

有了该辅助功能,我们可以按以下步骤将地址和城市分开:

    if ((ep = strchr (p, ','))) {   /* find ',' after city */
        memcpy (tmp, p, ep - p);    /* copy address & city to tmp */
        tmp[ep - p] = 0;            /* nul-terminate */
        /* split tmp into tokens checking for street ending ("Lane") */
        for (char *wp = strtok (tmp, " \n"); wp; wp = strtok (NULL, " \n")) {
            /* keep track of no. of chars added, check it fits -- here */
            strcat (company.addr, wp);      /* copy to address */
            if (streetending (wp)) {        /* check if street eding */
                wp += strlen (wp) + 1;      /* adv. past current word */
                while (!isalpha (*wp))      /* adv. to start of city */
                    wp++;
                strcpy (company.city, wp);  /* copy city to struct */
                break;  /* done */
            }
            strcat (company.addr, " "); /* not street ending, add space */
        }
    }

注意:,我们在城市名称上方使用了一个临时单词指针wp,将p指向',',这样我们就可以在那里将国家与邮编区分开来

接下来要做的是从p开始在字符串的其余部分中向前搜索,寻找下一个以State开头的字母字符,然后我们可以使用相同的“查找第一位”逻辑我们在上面用来定位地址的开头,在邮政编码的开头,例如

    while (!isalpha (*ep))  /* adv. endptr to start of state */
        ep++;
    p = ep;                 /* set pointer to start of state */

    if ((ep = strpbrk (ep, "0123456789"))) {    /* locate start of zip */
        char *zp = ep;                          /* set zip pointer */
        while (ep > p && !isalpha (*ep))        /* backup to end of state */
            ep--;
        ep++;
        if (ep - p <= COMP) {                   /* make sure city fits */
            memcpy (company.state, p, ep - p);  /* copy state to struct */
            company.state[ep - p + 1] = 0;      /* nul-terminate */
        }
        if (sscanf (zp, "%d", &company.zip) != 1) { /* convert zip to int */
            fputs ("error: invalid integer for zip.\n", stderr);
            return 1;
        }
    }

注意::另一个临时zip指针zp用于保存指向Zip开头的指针,然后再备份以找到状态的结尾)

基本上,这是将生产线最小化为所需部分的全部所需内容。 (如果公司名称中有数字,则由您自己决定是否添加该逻辑,依此类推...)将这个基本示例放在一起,您可以执行以下操作:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */
#define ADDR   30
#define NAME   20
#define COMP   15

typedef struct {        /* struct to hold address parts */
    char name[NAME+1],
        addr[ADDR+1],
        city[COMP+1],
        state[COMP+1];
        int zip;
} co_t;

/* simple function to look for word that is street ending */
int streetending (const char *s)
{
    char buf[MAXC], *p = buf;   /* temporary buf, convert s to lowercase */
    char *endings[] = { "boulevard", "drive", "lane", "street", NULL },
        **e = endings;  /* pointer to endings */

    strcpy (buf, s);    /* copy s to buf */

    while (*p) {        /* convert buf to all lowercase (for comparison) */
        *p = tolower (*p);
        p++;
    }

    while (*e) {        /* loop over street endings compare to buf */
        if (strcmp (buf, *e) == 0)  /* if match, return success */
            return 1;
        e++;            /* advance pointer to next ending */
    }

    return 0;   /* s not street ending, return failure */
}

int main (int argc, char **argv) {

    char buf[MAXC], *p = buf, *ep = buf, tmp[MAXC];
    co_t company = { .name = "" };
    /* 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 (!fgets (buf, MAXC, fp)) {   /* read entire line into buf */
        fputs ("(user canceled or stream error)\n", stderr);
        return 1;
    }
    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

    if ((p = strpbrk (p, "0123456789"))) {      /* locate 1st digit in buf */
        ep = p;                                 /* set endptr to pointer */
        while (ep > buf && !isalpha (*ep))      /* backup to find name end */
            ep--;
        ep++;                                   /* adv. to space after name */
        if (ep - buf <= NAME) {                 /* if name will fit */
            memcpy (company.name, buf, ep - buf);   /* copy to company.name */
            company.name[ep - buf + 1] = 0;         /* nul-terminate */
        }
    }
    else {  /* no number found, invalid input */
        fputs ("error: street number not found.\n", stderr);
        return 1;
    }

    if ((ep = strchr (p, ','))) {   /* find ',' after city */
        memcpy (tmp, p, ep - p);    /* copy address & city to tmp */
        tmp[ep - p] = 0;            /* nul-terminate */
        /* split tmp into tokens checking for street ending ("Lane") */
        for (char *wp = strtok (tmp, " \n"); wp; wp = strtok (NULL, " \n")) {
            /* keep track of no. of chars added, check it fits -- here */
            strcat (company.addr, wp);      /* copy to address */
            if (streetending (wp)) {        /* check if street eding */
                wp += strlen (wp) + 1;      /* adv. past current word */
                while (!isalpha (*wp))      /* adv. to start of city */
                    wp++;
                strcpy (company.city, wp);  /* copy city to struct */
                break;  /* done */
            }
            strcat (company.addr, " "); /* not street ending, add space */
        }
    }

    while (!isalpha (*ep))  /* adv. endptr to start of state */
        ep++;
    p = ep;                 /* set pointer to start of state */

    if ((ep = strpbrk (ep, "0123456789"))) {    /* locate start of zip */
        char *zp = ep;                          /* set zip pointer */
        while (ep > p && !isalpha (*ep))        /* backup to end of state */
            ep--;
        ep++;
        if (ep - p <= COMP) {                   /* make sure city fits */
            memcpy (company.state, p, ep - p);  /* copy state to struct */
            company.state[ep - p + 1] = 0;      /* nul-terminate */
        }
        if (sscanf (zp, "%d", &company.zip) != 1) { /* convert zip to int */
            fputs ("error: invalid integer for zip.\n", stderr);
            return 1;
        }
    }
    /* if it all worked, your values should be nicely separated */
    printf ("'%s'\n'%s'\n'%s'\n'%s'\n%d\n", 
            company.name, company.addr, company.city, 
            company.state, company.zip);

    return 0;
}

使用/输出示例

使用存储在文件dat/coaddress.txt中的输入行,并在所有字符串字段周围添加单引号来标记提取的字符串,然后在输入上运行程序将提供:

$ ./bin/companynameaddr dat/coaddress.txt
'Sabre Corporation'
'15790 West Henness Lane'
'New Corio'
'New Mexico'
65790

读取一行或一千行都是相同的。该代码的唯一区别是将处理包裹在while (fgets (buf, MAXC, fp)) { ...循环中,为结构数组保留索引,并将文件的结尾移到末尾。

仔细研究。有很多很多方法可以做到这一点。我们所做的基本上称为“遍历字符串”,其中,您基本上是在字符串下方“一对虫”一对指针以提取所需信息。我们使用strpbrkstrchr帮助推进指针,并且让strtok帮助分离一个临时缓冲区,寻找街道结尾词"Lane",以确定街道的终点和城市开始。您可以至少执行10种不同的方式。如果您对以上操作还有其他疑问,请告诉我。