c - C程序读取文件读取额外的一行

标签 c file io

我正在处理的代码涉及读取带有输入的文件,其结构如下:

(spaces)name(spaces) val (whatever) \n
(spaces)name(spaces) val (whatever) \n
(spaces)name(spaces) val (whatever) \n


其中空格表示任意数量的空格。我的代码应该给出名称和值。还有另一种情况,即忽略#号后的所有行(就像注释一样)。输出应该是:

"name: (name) value: val \n" 


对于大多数代码来说,它是有效的,除了它添加了额外的一行,它将在最后读取的数字之前创建set name = null和val。例如我的测试文件:

a 12
b     33
#c 15
nice 6#9


输出为:

Line after:  a 12

name: a value: 12 :
Line after: b     33 

name: b  value: 33 :
Line after:  # c 15

Line after:  nice 6#9

name: nice value: 6 :
Line after:

name:  value: 6 : //why is this happening


代码在这里。

void readLine(char *filename)
{
    FILE *pf;
    char name[10000]; 
    char value[20];
    pf = fopen(filename, "r");
    char line[10000];
    if (pf){
        while (fgets(line, sizeof(line), pf) != NULL) {
            //printf("Line: %s\n",line);            
                printf("Line after: %s\n",line); 
                while(true){
                    int i=0;
                    char c=line[i]; //parse every char of the line
                            //assert(c==' ');
                            int locationS=0; //index in the name
                            int locationV=0; //index in the value
                            while((c==' ')&& i<sizeof(line)){
                                //look for next sequence of chars
                                ++i;
                                c=line[i];
                                if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c!=' ');
                            while (c!=' '&&i<sizeof(line))
                            {
                                name[locationS]=c;
                                locationS++;
                                //printf("%d",locationS);
                                ++i;
                                c=line[i];if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c==' ');
                            while(c==' '&&i<sizeof(line)){
                                //look for next sequence of chars
                                ++i;
                                c=line[i];
                                if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c!=' ');
                            printf("\n");
                             while ((c!=' '&& c!='\n')&&i<sizeof(line))
                            {
                                value[locationV]=c;
                                locationV++;
                                ++i;
                                c=line[i];if(c=='#'){
                                    break;
                                }
                            }
                            printf("name: %s value: %s : \n",name, value);
                            memset(&name[0], 0, sizeof(name));
                            memset(&value[0], 0, sizeof(value));
                            break; //nothing interesting left
                }
        }
        fclose(pf);
    }else{
        printf("Error in file\n");
        exit(EXIT_FAILURE);
    }
}

最佳答案

帕夏(Pasha),您在正确地做一些事情,但随后却使您想做的事情变得更加困难。首先,避免在代码中使用幻数,例如char name[10000];。代替:

...
#define MAXC 1024   /* if you need a constant, #define one (or more) */

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

    char line[MAXC];
    ...


(遵循“不要忽略缓冲区大小:)

同样,在尝试使用fgets()读取文件之前,您在打开文件并验证文件已打开以进行读取方面做得很好。您可以在单个块中进行验证并在那时处理错误-这样可以减少整个代码其余部分的缩进程度,例如

    /* 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;
    }


现在,打开文件并验证该文件可以读取并处理了任何错误,您可以继续读取文件中的每一行。除非您将名称存储在需要保留读取循环的数组中,否则可以在读取循环块中简单声明name[MAXC];,例如

    while (fgets (line, MAXC, fp)) {    /* read each line of input */
        char name[MAXC];                /* storage for name */
        int val;                        /* integer value for val */


(注意:我们没有声明另一个数组来保存值,而是简单地将val声明为int,并且将使用sscanf解析name并将val当时的值直接转换为int

每当您使用面向行的输入功能(例如fgets()或POSIX getline())时,您都希望修整读取的'\n'并将其包含在填充的缓冲区中。您可以使用strcspn轻松地做到这一点。 (请参见strspn(3) - Linux manual page。这是一个简单的单次调用,您可以使用strcspn的返回值作为'\n'的索引,以便使用以n终止的字符(即'\n' ,或简单地'\0'

        line[strcspn (line, "\n")] = 0; /* trim '\n' from end of line */


现在,您需要做的就是检查0中第一个'#'的存在,该标记标志注释的开始。如果找到,您将像对line那样简单地用n终止字符覆盖'#'

        line[strcspn (line, "#")] = 0;  /* overwrite '#' with nul-char */


现在您已经有了一行,并删除了'\n'和可能出现的所有注释,您可以检查'\n'是否为空(这意味着它以line开头或只是一个仅包含一个'#'

        if (!*line)                     /* if empty-string */
            continue;                   /* get next line */


(注意:'\n'只是if (!*line)的简写。当取消引用缓冲区时,例如if (line[0] == 0),您只是以指针表示形式将第一个元素(第一个字符)作为*line返回,与数组中的*line == *(line + 0)等效。 -index表示法。*(line + 0) == line[0]也用作取消引用。)

现在,只需使用[]直接从name解析vallinesscanf"%s"转换说明符都将忽略转换说明符之前的所有前导空格。只要"%d"本身不包含空格,就可以使用此简单方法。就像验证文件打开的返回结果一样,您也将验证name的返回结果,以确定您指定的转换次数是否成功发生。例如:

        if (sscanf (line, "%1023s %d", name, &val) == 2)  /* have name/value? */
            printf ("\nline: %s\nname: %s\nval : %d\n", line, name, val);
        else
            printf ("\nline: %s (doesn't contain name/value\n", line);


(请注意:通过对字符串使用field-width修饰符(例如sscanf),可以保护"%1023s"的数组边界。字段宽度限制了name不得将多个sscanf写入名称。这无法提供变量或宏,这是必须在代码中加上一个魔术数字的场合之一。对于每个规则,通常都有一两个警告。)

如果您要求进行两次转换,并且1023 char + \0返回了sscanf,则您知道请求的两次转换均成功。此外,由于您为2指定了整数转换,因此可以保证该值将包含一个整数。

这里的所有都是它的。剩下的就是关闭文件(如果不从val读取),则操作完成。一个完整的例子可能是:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */

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

    char line[MAXC];
    /* 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;
    }

    while (fgets (line, MAXC, fp)) {    /* read each line of input */
        char name[MAXC];                /* storage for name */
        int val;                        /* integer value for val */

        line[strcspn (line, "\n")] = 0; /* trim '\n' from end of line */
        line[strcspn (line, "#")] = 0;  /* overwrite '#' with nul-char */

        if (!*line)                     /* if empty-string */
            continue;                   /* get next line */

        if (sscanf (line, "%1023s %d", name, &val) == 2)  /* have name/value? */
            printf ("\nline: %s\nname: %s\nval : %d\n", line, name, val);
        else
            printf ("\nline: %s (doesn't contain name/value\n", line);
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
}


(注意:如果要在修整stdin和注释之前打印原始的line,只需在调用'\n'之前将line的打印内容移动。strcspn上方的内容将显示< cc>在调用line之前)

使用/输出示例

使用存储在系统上line中的输入文件,您只需执行以下操作即可读取从sscanf重定向的值:

$ ./bin/parsenameval <dat/nameval.txt

line: a 12
name: a
val : 12

line: b     33
name: b
val : 33

line: nice 6
name: nice
val : 6


(请注意:只需删除重定向dat/nameval.txt即可真正打开并从文件中读取,而不必让Shell为您完成。六对一,六对六。)

仔细检查一下,如果您还有其他问题,请告诉我。如果由于某种原因您不能使用任何函数来帮助您解析该行并且必须仅使用指针或数组索引,请告诉我。按照上述方法,只需很少的精力即可将每个操作替换为其手动操作。

关于c - C程序读取文件读取额外的一行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58686673/

相关文章:

C - 十六进制输出中缺少 0

c - Makefile C 包含已安装库的头文件并链接到库的最佳方法

c# - 删除文件并重新创建文件后,不更改 Windows 中的创建日期

java - Java 中的流链接

java - 检查字符串是否为 null 或空格

c++ - 将 SFML 客户端连接到 C 服务器

c# - 函数中过早返回的效率

android - 我在哪里可以找到 Android 中的应用程序数据

java - 修改Java中的隐藏文件

java - Scala/Java - 未在指定目录内创建文件