【问题标题】:reading from multiple lines of a file - scanf in c using Linux cat从文件的多行读取 - 使用 Linux cat 在 c 中的 scanf
【发布时间】:2021-04-02 20:30:38
【问题描述】:

我想在 Linux 命令行中以类似流的方式从日志文件中读取,其中日志文件如下所示:

=== Start ===
I 322334bbaff, 4
I 322334bba0a, 4
 S ff233400ff, 8
I 000004bbaff, 4
L 322334bba0a, 4
=== End ===

我有一个 c 文件来读取 log file 的每一行,并在每个符合条件的行中存储内存地址及其大小(例如,322334bba0a4)。

// my_c.c
#include <stdio.h>
#include <unistd.h>

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

    if (!isatty(fileno(stdin))) {

    int long long addr; 
    int size; 
    char func;

    while(scanf("%c %llx, %d\n",&func, &addr, &size))
    {
         if(func=='I')
           {
    fprintf(stdout, "%llx ---- %d\n", addr,size);
           }
    }
  }
    return 0;
}

因为它应该作为流工作,所以我必须使用管道:

$ cat log 2>&1 | ./my_c

之所以使用2&gt;&amp;1,是因为替换cat log的主要过程是从stderr中的valgrind工具跟踪的程序。

./my_c 只读取log file 的第一行。我希望读取通过管道的每一行并存储内存地址和行的大小。

我对 c 编程非常陌生,并且已经搜索了很多方法来解决这个问题。目前的代码是我到目前为止想出的。

任何帮助将不胜感激。

【问题讨论】:

  • 看来您可能需要考虑潜在的领先空间。
  • 提示:不要将变量定义放在一行中。它只是让我们更难看到发生了什么。
  • 我编辑了文件
  • 您是否尝试搜索“如何在 C 中编写循环?”尽管如果您不知道答案,则需要阅读介绍性文字。 SO 和 internet sn-ps 都不能给你一个基础。
  • 你需要检查scanf返回的值。如果输入的第一行是=== Start ===,那么我希望scanf("%c %llx, %d\n",&amp;func, &amp;addr, &amp;size) 在第一次调用时只匹配一个转换说明符。它将消耗一个=。第二次调用将消耗下一个=

标签: c linux scanf


【解决方案1】:

我建议使用 getline() 读取每一行,然后使用 sscanf() 或自定义解析函数对其进行解析。

// SPDX-License-Identifier: CC0-1.0
#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    char   *linebuf = NULL;
    size_t  linemax = 0;
    ssize_t linelen;

    while (1) {
        char            type[2];
        unsigned long   addr;
        size_t          len;
        char            dummy;

        linelen = getline(&linebuf, &linemax, stdin);
        if (linelen == -1)
            break;

        if (sscanf(linebuf, "%1s %lx, %zu %c", type, &addr, &len, &dummy) == 3) {
            printf("type[0]=='%c', addr==0x%lx, len==%zu\n", type[0], addr, len);
        }
    }

    /* Optional: Discard used line buffer. Note: free(NULL) is safe. */
    free(linebuf);
    linebuf = NULL;
    linemax = 0;

    /* Check if getline() failed due to end-of-file, or due to an error. */
    if (!feof(stdin) || ferror(stdin)) {
        fprintf(stderr, "Error reading from standard input.\n");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

上面,linebuf 是动态分配的缓冲区,linemax 是为其分配的内存量。 getline() 没有行长限制,除了可用内存。

因为%1s(单字符标记)用于解析第一个标识符字母,所以忽略它之前的任何空格。 (除%c%n 之外的所有转换都会静默跳过前导空格。)

%lx 将下一个标记作为十六进制数转换为unsigned long(与unsigned long int 完全相同)。

%zu 将下一个标记作为十进制非负数转换为size_t

最后的%c(转换为char)是一个假捕手;它不应该转换任何东西,但如果确实如此,则意味着在线上有额外的东西。它前面有一个空格,因为我们有意在转换后跳过空格。

(scanf()/sscanf() 转换模式中的空格表示在该点跳过任意数量的空格,包括 none。)

scanf 系列函数的结果是成功转换的次数。因此,如果该行具有预期的格式,我们会得到3。 (如果行中有额外的东西,它将是4,因为虚拟字符转换了一些东西。)

此示例程序仅打印出解析后的type[0]addrlen 的值,因此您可以轻松地将其替换为您需要的任何if (type[0] == ...)switch (type[0]) { ... } 逻辑。

由于行缓冲区是动态分配的,因此最好将其丢弃。我们确实需要将缓冲区指针初始化为NULL 并将其大小初始化为0,以便getline() 将分配初始缓冲区,但我们不一定需要释放缓冲区,因为操作系统会自动释放进程使用的所有内存。这就是为什么我添加了关于丢弃行缓冲区是可选的注释的原因。 (幸运的是,free(NULL) 是安全的,什么也不做,所以我们只需要将free(linebuf) 设置为linebufNULLlinemax0,我们甚至可以重用缓冲区。真的,我们甚至可以在 getline() 之前完全安全地这样做。因此,这是一个很好的动态内存管理示例:没有行长限制!)


记住每个内存引用以进行某种处理,我们真的不需要做太多额外的工作:

// SPDX-License-Identifier: CC0-1.0
#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <stdio.h>

struct memref {
    size_t          addr;
    size_t          len;
    int             type;
};

struct memref_array {
    size_t          max;    /* Number of memory references allocated */
    size_t          num;    /* Number of memory references used */
    struct memref  *ref;    /* Dynamically allocated array of memory references */
};
#define MEMREF_ARRAY_INIT  { 0, 0, NULL }

static inline int  memref_array_add(struct memref_array *mra, size_t addr, size_t len, int type)
{
    /* Make sure we have a non-NULL pointer to a memref_array structure. */
    if (!mra)
        return -1;

    /* Make sure we have room for at least one more memref structure. */
    if (mra->num >= mra->max) {
        size_t          new_max;
        struct memref  *new_ref;

        /* Growth policy.  We need new_max to be at least mra->num + 1.
           Reallocation is "slow", so we want to allocate extra entries;
           but we don't want to allocate so much we waste oodles of memory.
           There are many possible allocation strategies, and which one is "best"
           -- really, most suited for a task at hand --, varies!
           This one uses a simple "always allocate 3999 extra entries" policy. */
        new_max = mra->num + 4000;

        new_ref = realloc(mra->ref, new_max * sizeof mra->ref[0]);
        if (!new_ref) {
            /* Reallocation failed.  Old data still exists, we just didn't get
               more memory for the new data.  This function just returns -2 to
               the caller; other options would be to print an error message and
               exit()/abort() the program. */
            return -2;
        }

        mra->max = new_max;
        mra->ref = new_ref;
    }

    /* Fill in the fields, */
    mra->ref[mra->num].addr = addr;
    mra->ref[mra->num].len  = len;
    mra->ref[mra->num].type = type;
    /* and update the number of memory references in the table. */
    mra->num++;

    /* This function returns 0 for success. */
    return 0;
}

int main(void)
{
    struct memref_array  memrefs = MEMREF_ARRAY_INIT;

    char                *linebuf = NULL;
    size_t               linemax = 0;
    ssize_t              linelen;

    while (1) {
        char            type[2];
        unsigned long   addr;
        size_t          len;
        char            dummy;

        linelen = getline(&linebuf, &linemax, stdin);
        if (linelen == -1)
            break;

        if (sscanf(linebuf, "%1s %lx, %zu %c", type, &addr, &len, &dummy) == 3) {
            if (memref_array_add(&memrefs, (size_t)addr, len, type[0])) {
                fprintf(stderr, "Out of memory.\n");
                return EXIT_FAILURE;
            }
        }
    }

    /* Optional: Discard used line buffer. Note: free(NULL) is safe. */
    free(linebuf);
    linebuf = NULL;
    linemax = 0;

    /* Check if getline() failed due to end-of-file, or due to an error. */
    if (!feof(stdin) || ferror(stdin)) {
        fprintf(stderr, "Error reading from standard input.\n");
        return EXIT_FAILURE;
    }

    /* Print the number of entries stored. */
    printf("Read %zu memory references:\n", memrefs.num);
    for (size_t i = 0; i < memrefs.num; i++) {
        printf("    addr=0x%lx, len=%zu, type='%c'\n",
               (unsigned long)memrefs.ref[i].addr,
               memrefs.ref[i].len,
               memrefs.ref[i].type);
    }

    return EXIT_SUCCESS;
}

新的memref 结构描述了我们读取的每个内存引用,memref_array 结构包含动态分配的数组。 num 成员是数组中的引用数,max 成员是我们为其分配内存的引用数。

memref_array_add() 函数接受一个指向memref_array 的指针,并填写三个值。因为 C 是按值传递函数参数的——也就是说,改变函数中的参数值并不会改变函数中的变量呼叫者! – 我们需要传递一个指针,以便通过指针进行更改,这些更改在调用者中也是可见的。这就是 C 的工作原理。

在那个函数中,我们需要自己处理内存管理。因为我们使用MEMREF_ARRAY_INIT 将内存引用数组初始化为已知的安全值,所以我们可以在需要时使用realloc() 来调整数组指针的大小。 (本质上,realloc(NULL, size) 的工作方式与malloc(size) 完全相同。)

在主程序中,我们在 if 子句中调用该函数。 if (x)if (x != 0) 相同,即。如果x 不为零,则执行主体。因为memref_array_add() 成功返回零,错误返回非零,if (memref_array_add(...)) 表示“如果 memref_array_add 调用失败,则”

请注意,我们根本不会丢弃程序中的内存引用数组。我们不需要,因为操作系统会为我们释放它。但是,如果程序在不再需要内存引用数组后确实做了进一步的工作,那么丢弃它是有意义的。我打赌你猜到这就像丢弃 getline() 使用的行缓冲区一样简单:

static inline void memref_array_free(struct memref_array *mra)
{
    if (mra) {
        free(mra->ref);
        mra->max = 0;
        mra->num = 0;
        mra->ref = NULL;
    }
}

所以在主程序中,memref_array_free(&amp;memrefs); 就足够了。

函数定义前面的static inline 只是告诉编译器在它们的调用位置内联函数,而不是为它们生成可链接符号。如果你愿意,你可以省略它们;我用它们来表示这些是只能在这个文件(或编译单元)中使用的辅助函数。

之所以在主程序中使用memrefs.member,而在辅助函数中使用mra-&gt;member,是因为mra是指向结构的指针,而memrefs是结构类型的变量。同样,只是一个 C 怪癖。 (我们也可以写(&amp;memrefs)-&gt;member(*mra).member。)

这可能比你想读的要多得多(不仅仅是 OP,而是,亲爱的读者),但我一直认为动态内存管理应该尽早展示给新的 C 程序员,让他们自信地掌握它们,而不是认为它很难/不值得。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-16
    • 1970-01-01
    • 1970-01-01
    • 2014-05-04
    相关资源
    最近更新 更多