簡體   English   中英

C 程序讀取文件讀取額外行

[英]C program to read file reading an extra line

我正在處理的代碼涉及讀取具有以下結構的輸入文件:

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

其中空格表示任意數量的空格。 我的代碼應該同時給出名稱和值。 還有另一種情況,在“#”之后的行中的所有內容都被忽略(視為注釋)。 output 應該是:

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

大多數情況下,代碼都在工作,除了它添加了一個額外的行,它將創建一個集合 name= null 和 val 到最后讀取的數字。 例如我的測試文件:

a 12
b     33
#c 15
nice 6#9

output 是:

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);
    }
}

帕夏,你在做一些正確的事情,但是你正在讓你想做的事情變得比需要的困難得多。 首先,避免在代碼中使用幻數,例如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()讀取文件之前打開文件並驗證文件是否已打開以供讀取方面做得很好。 您可以在單個塊中進行該驗證並在當時處理錯誤 - 這將具有減少整個代碼 rest 的一級縮進的效果,例如

    /* 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解析nameval將值直接轉換為int

每當您使用面向行的輸入 function (如fgets()或 POSIX getline()時,您將需要修剪'\n'讀取並包含在已填充的緩沖區中。您可以使用strcspn輕松做到這一點,請參閱strspn(3) - Linux 手冊頁。這是一個簡單的單一調用,您使用strcspn的返回值作為'\n'的索引,以便用nul 終止字符覆蓋'\n' (是'\0' ,或者只是0

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

現在您需要做的就是檢查標記注釋開始的line中的第一個'#'是否存在。 如果找到,您只需使用nul 終止字符覆蓋'#' ,就像對'\n'所做的那樣,例如

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

現在您有了自己的行並刪除了'\n'和任何可能存在的注釋,您可以檢查該line是否為空(這意味着它以'#'開頭,或者只是一個僅包含'\n'的空行'\n' )

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

(注意: if (!*line)只是if (line[0] == 0)的簡寫。當您取消引用緩沖區時,例如*line您只是將第一個元素(第一個字符)返回為*line == *(line + 0)在指針表示法中等效於*(line + 0) == line[0]在數組索引表示法中。 []也作為解引用。)

現在只需使用sscanf直接從line中解析nameval "%s""%d"轉換說明符都將忽略轉換說明符之前的所有前導空格。 只要name本身不包含空格,您就可以使用這個簡單的方法。 正如您驗證文件打開的返回一樣,您將驗證sscanf的返回以確定您指定的成功轉換次數是否發生。 例如:

        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);

注意:通過對字符串使用字段寬度修飾符,例如"%1023s" ,您可以保護name的數組邊界。字段寬度限制sscanf將超過1023 char + \0寫入名稱。這不能由一個變量或一個宏,並且是您必須在代碼中粘貼一個幻數的情況之一......對於每條規則,通常都有一兩個警告......)

如果您要求進行 2 次轉換,並且sscanf返回2 ,那么您就知道所請求的兩次轉換都成功了。 此外,由於您已經為val指定了integer轉換,因此可以保證該值將包含integer

這里的所有都是它的。 剩下的就是關閉文件(如果不是從stdin讀取),你就完成了。 一個完整的例子可能是:

#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);
}

注意:如果要在修剪'\n'和注釋之前打印原始line ,只需在調用strcspn之前移動打印line 。上面的line打印顯示調用sscanf之前line的最終 state )

示例使用/輸出

使用存儲在我系統上的dat/nameval.txt中的輸入文件,您可以簡單地執行以下操作來讀取從stdin重定向的值:

$ ./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

注意:只需刪除重定向<以實際打開並從文件中讀取,而不是讓 shell 為您執行此操作。六對一,六對一。)

如果您還有其他問題,請仔細查看並告訴我。 如果由於某種原因您不能使用任何 function 來幫助您解析行並且只能使用指針或數組索引,請告訴我。 按照上面的方法,只需一點點努力就可以將每個操作替換為手動等效操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM