【问题标题】:How to take text file as command line argument in C如何在 C 中将文本文件作为命令行参数
【发布时间】:2019-08-14 02:23:03
【问题描述】:

基本上,我试图接受一个命令行文本文件,这样当我将程序作为“程序指令.txt”运行时,它会存储指令中列出的值。但是我无法测试我目前拥有的东西,因为它给了我错误“分段错误核心转储”。

int main(int argc, char* argv[]) {
    setup_screen();
    setup();

    // File input
    char textExtracted[250];
    FILE* file_handle;
    file_handle = fopen(argv[1], "r");
    while(!feof(file_handle)){
        fgets(textExtracted, 150, file_handle);
    }

    fclose(file_handle);

    printf("%s", textExtracted[0]);

    return 0;   
}

文本文件里面是

A 1 2 3 4 5
B 0 0
C 1 1

我只是试图将每一行存储在一个数组中,然后打印它们。

【问题讨论】:

  • 您没有收到编译器的警告? (对于 gcc 和 clang,请确保您至少使用 -Wall -Wextra 进行编译以获得一组好的警告)。
  • 您将单个字符textExtracted[0] 传递给printf(),但由于%s 而告诉它您已经传递了一个字符串(指向char 的指针); printf() 在你撒谎时会心烦意乱。使用while (!feof(file)) is always wrong;使用while (fgets(textExtracted, sizeof(textExtracted), file_handle) 控制循环并忘记feof() 的存在。此外,每一行都会覆盖前一行,因此您只打印读取的最后一行。但是格式字符串不匹配会导致崩溃。
  • 您还应该在打开文件之前检查是否有argv[1] 可供使用。打开文件后,您应该在程序中使用文件流 (file_handle) 之前检查是否成功。只有在成功打开流时才应关闭它。
  • 一种方便的方式一次完成所有操作(如果没有给出参数,使用stdin)是使用 ternary 表达式,例如file_handle = argc > 1 ? fopen(argv[1], "r") : stdin;(然后验证 if (!file_handle) { fprintf (stderr, "error: file not found '%s'\n", argv[1]); return 1; } 另外,您如何建议将字符和数字一起存储在一个数组中?您是否只想将每一行存储为 char * 以 nul 结尾的字符串,然后将数字转换为整数值根据需要?

标签: c


【解决方案1】:

几点:

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

我建议你在继续之前检查这里的参数数量

setup_screen();
setup();

// File input
char textExtracted[250];

可以加入声明,但始终检查来自 I/O 的返回值

FILE* file_handle = = fopen(argv[1], "r");
if (NULL == file_handle)
{
  perror(argv[1]);
  return EXIT_FAILURE;
}

下面不是从文件中读取的正确方法,而是应该 首先尝试从文件中读取,然后检查读取的错误/eof/enuff 字节

// while(fgets(textExtracted,sizeof(textExtracted), 1, file_handle) > 0) {}

while(!feof(file_handle)){
    fgets(textExtracted, 150, file_handle);
}

看起来你认为 fgets 在你调用它时会附加到 textExtracted 多次,它没有!文件中的每一行都将覆盖先前读取的行。另请注意,\n 字符包含在您的缓冲区中。

但是由于您的文件看起来很小,您可以阅读整个文件 内容到您的缓冲区并使用它。

// int size = fread(textExtracted, sizeof(textExtracted), 1, file_handle);

最好先检查文件的大小,然后使用 malloc 分配一个缓冲区来保存整个文件,或者逐个字符地读取文件,然后即时执行您需要执行的任何命令。例如switch 语句作为状态机非常棒

switch( myreadchar )
{
   case 'A':
     break;
   case 'B':
     break;
   ...
}

textExtracted[0] 是一个字符,textExtracted 是整个数组所以而不是

printf("%s", textExtracted[0]);

printf("%s", textExtracted);

甚至更好

fputs(textExtracted, stdout);

return 0;   

【讨论】:

    【解决方案2】:

    您提出的问题是“我如何从文件中读取未知数量的未知长度的行?”的经典问题您以内存有效的方式解决问题的方法是声明一个指向char 的指针并分配合理数量的指针以开始,然后为每一行分配起始地址,当您读取每一行并为其分配时,将保存每一行的块的起始地址分配给您的指针。

    一种有效的方法是使用 fgets 或使用 POSIX getline 将文件中的每一行读入一个固定缓冲区(大小足以容纳最长的行而不会跳过),这将根据需要分配给坚持到底。然后,您从临时缓冲区中删除尾随 '\n' 并获取该行的长度。

    然后分配length + 1 字符的内存块(+1 用于 nul-terminating 字符)并将新内存块的地址分配给下一个可用指针(保持跟踪分配的指针数量和使用的指针数量)

    当使用的指针数量等于分配的数量时,您只需 realloc 附加指针(通常通过将当前可用数量加倍,或分配一些固定数量的附加指针)然后继续。根据需要重复该过程多次,直到您读取了输入文件中的所有行。

    有很多方法可以实现它并安排不同的任务,但基本上都归结为同一件事。从一个合理大小的临时缓冲区开始处理最长的行(不要吝啬,以防输入数据有一些变化——1K 的缓冲区是便宜的保险,根据需要进行调整)。添加您的计数器以跟踪分配的指针数量以及使用的数量(您的索引)。打开并验证命令行上给出的文件是否打开以供读取(如果命令行上没有给出参数,则默认从stdin读取)例如,您可以这样做:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXC 1024   /* if you need a constant, #define one (or more) */
    
    int main (int argc, char **argv) {
    
        char buf[MAXC] = "",        /* temp buffer to hold line read from file */
             **lines = NULL;        /* pointer-to-pointer to each line */
        size_t ndx = 0, alloced = 0;    /* current index, no. of ptrs allocated */
        /* 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;
        }
        ...
    

    打开并验证文件后,您已准备好读取每一行,使用 read 函数本身控制读取循环,并按照上面的大纲处理每一行的存储,例如

        while (fgets (buf, MAXC, fp)) {     /* read each line */
            size_t len;                     /* for line length */
            if (ndx == alloced) {           /* check if realloc needed */
                void *tmp = realloc (lines, /* alloc 2X current, or 2 1st time */
                                    (alloced ? 2 * alloced : 2) * sizeof *lines);
                if (!tmp) {                 /* validate EVERY allocation */
                    perror ("realloc-lines");
                    break;  /* if allocation failed, data in lines still good */
                }
                lines = tmp;    /* assign new block of mem to lines */
                alloced = alloced ? 2 * alloced : 2;    /* update ptrs alloced */
            }
    

    注意: 上面,在你的读取循环中发生的第一件事是检查你是否有可用的指针,例如if (ndx == alloced),如果您的索引(使用的编号)等于分配的编号,则您重新分配更多。 alloced ? 2 * alloced : 2 上面的 三元 只是询问您是否有一些先前分配的 alloced ? 然后将 2 * alloced 的数字加倍,否则(:)只需从 2 指针开始并从那里开始。在该加倍方案中,您在每次连续重新分配时分配 2, 4, 8, 16, ... 指针。

    另请注意:当您调用 realloc 时,您始终使用临时指针,例如tmp = realloc (lines, ...) 而你从不 realloc 使用指针本身,例如lines = realloc (lines, ...)。当(不是如果)realloc 失败时,它返回NULL,如果你将它分配给你的原始指针——你刚刚创建了一个内存泄漏,因为lines 的地址已经丢失,这意味着你无法到达或@ 987654345@你之前分配的内存。

    现在您已经确认您有一个可用的指针来分配内存块的地址来保存该行,从buf 中删除'\n' 并获取该行的长度。您可以在一次调用 strcspn 时方便地做到这一点,它返回字符串中不包含分隔符 "\n" 的初始字符数,例如

            buf[(len = strcspn(buf, "\n"))] = 0;    /* trim \n, get length */
    

    注意:上面你只是用空终止字符0覆盖'\n',相当于'\0'

    现在您有了行的长度,您只需分配 length + 1 字符并从临时缓冲区 buf 复制到您的新内存块,例如

            if (!(lines[ndx] = malloc (len + 1))) { /* allocate for lines[ndx] */
                perror ("malloc-lines[ndx]");       /* validate combined above */
                break;
            }
            memcpy (lines[ndx++], buf, len + 1);    /* copy buf to lines[ndx] */
        }                                           /* increment ndx */
    

    此时您已完成所有行的读取和存储,如果未从 stdin 读取,则可以简单地关闭文件。在这里,例如,我们只是输出每一行,然后free 为每一行存储,最后也为分配的指针释放内存,例如

        if (fp != stdin) fclose (fp);   /* close file if not stdin */
    
        for (size_t i = 0; i < ndx; i++) {      /* loop over each storage line */
            printf ("lines[%2zu] : %s\n", i, lines[i]); /* output line */
            free (lines[i]);    /* free storage for strings */
        }
        free (lines);           /* free pointers */
    }
    

    就是这样。总而言之,您可以这样做:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXC 1024   /* if you need a constant, #define one (or more) */
    
    int main (int argc, char **argv) {
    
        char buf[MAXC] = "",        /* temp buffer to hold line read from file */
             **lines = NULL;        /* pointer-to-pointer to each line */
        size_t ndx = 0, alloced = 0;    /* current index, no. of ptrs allocated */
        /* 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 (buf, MAXC, fp)) {     /* read each line */
            size_t len;                     /* for line length */
            if (ndx == alloced) {           /* check if realloc needed */
                void *tmp = realloc (lines, /* alloc 2X current, or 2 1st time */
                                    (alloced ? 2 * alloced : 2) * sizeof *lines);
                if (!tmp) {                 /* validate EVERY allocation */
                    perror ("realloc-lines");
                    break;  /* if allocation failed, data in lines still good */
                }
                lines = tmp;    /* assign new block of mem to lines */
                alloced = alloced ? 2 * alloced : 2;    /* update ptrs alloced */
            }
            buf[(len = strcspn(buf, "\n"))] = 0;    /* trim \n, get length */
            if (!(lines[ndx] = malloc (len + 1))) { /* allocate for lines[ndx] */
                perror ("malloc-lines[ndx]");       /* validate combined above */
                break;
            }
            memcpy (lines[ndx++], buf, len + 1);    /* copy buf to lines[ndx] */
        }                                           /* increment ndx */
    
        if (fp != stdin) fclose (fp);   /* close file if not stdin */
    
        for (size_t i = 0; i < ndx; i++) {      /* loop over each storage line */
            printf ("lines[%2zu] : %s\n", i, lines[i]); /* output line */
            free (lines[i]);    /* free storage for strings */
        }
        free (lines);           /* free pointers */
    }
    

    使用/输出示例

    $ ./bin/fgets_lines_dyn dat/cmdlinefile.txt
    lines[ 0] : A 1 2 3 4 5
    lines[ 1] : B 0 0
    lines[ 2] : C 1 1
    

    stdin 重定向而不是打开文件:

    $ ./bin/fgets_lines_dyn < dat/cmdlinefile.txt
    lines[ 0] : A 1 2 3 4 5
    lines[ 1] : B 0 0
    lines[ 2] : C 1 1
    

    内存使用/错误检查

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

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

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

    $ valgrind ./bin/fgets_lines_dyn dat/cmdlinefile.txt
    ==6852== Memcheck, a memory error detector
    ==6852== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==6852== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
    ==6852== Command: ./bin/fgets_lines_dyn dat/cmdlinefile.txt
    ==6852==
    lines[ 0] : A 1 2 3 4 5
    lines[ 1] : B 0 0
    lines[ 2] : C 1 1
    ==6852==
    ==6852== HEAP SUMMARY:
    ==6852==     in use at exit: 0 bytes in 0 blocks
    ==6852==   total heap usage: 6 allocs, 6 frees, 624 bytes allocated
    ==6852==
    ==6852== All heap blocks were freed -- no leaks are possible
    ==6852==
    ==6852== For counts of detected and suppressed errors, rerun with: -v
    ==6852== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

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

    虽然为每个指针和每一行分配存储空间一开始可能看起来令人生畏,但无论是从文件中读取和存储行、整数还是浮点值,您都会一遍又一遍地面临这个问题。数据等……值得花时间学习。您的替代方法是声明一个固定大小的二维数组,并希望您的行长永远不会超过声明的宽度,并且行数永远不会超过您声明的行数。 (您也应该了解这一点,但限制很快就会显现出来)

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

    【讨论】:

      猜你喜欢
      • 2017-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-17
      • 2020-06-19
      • 1970-01-01
      • 2017-12-17
      • 1970-01-01
      相关资源
      最近更新 更多