从文件的多行读取 - 在 c 中使用 Linux cat 的 scanf

reading from multiple lines of a file - scanf in c using Linux cat

我想在 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>&1 是因为替换 cat log 的主要过程是来自 stderr.

中的 valgrind 工具的程序跟踪

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

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

非常感谢任何帮助。

我建议使用 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(一个字符的token)用于解析第一个标识符字母,所以忽略它之前的任何白色space。 (除了 %c%n 之外的所有转换都会自动跳过前导白色 space。)

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

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

最后的%c(转换为char)是一个虚拟捕手;它不应该转换任何东西,但如果它转换了,就意味着线路上有额外的东西。它前面有一个space,因为我们有意要在转换后跳过whitespace。

(scanf()/sscanf() 转换模式中的 space 表示在该点跳过任意数量的白色 space,包括 none .)

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

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

由于行缓冲区是动态分配的,因此最好丢弃它。我们确实需要将缓冲区指针初始化为 NULL 并将其大小初始化为 0,以便 getline() 将分配初始缓冲区,但我们不一定 需要 释放缓冲区,因为 OS 将自动释放进程使用的所有内存。这就是为什么我添加了关于丢弃可选行缓冲区的注释。 (幸运的是,free(NULL)是安全的,什么都不做,所以我们需要做的就是free(linebuf),并将linebuf设置为NULL,将linemax设置为0,我们甚至可以重用缓冲区。真的,我们甚至可以在 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() returns 如果成功则为零,如果错误则为非零,所以 if (memref_array_add(...)) 表示 “如果 memref_array_add 调用失败,则”

请注意,我们根本没有丢弃程序中的内存引用数组。我们不需要,因为OS会为我们释放它。但是,如果程序在不再需要内存引用数组后确实做了进一步的工作,那么丢弃它是有意义的。我打赌你猜到这就像丢弃 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(&memrefs);就足够了。

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

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

这可能比你想读的要多得多(不仅仅是OP,还有,亲爱的reader),但我一直觉得动态记忆management 应该尽早展示给新手 C 程序员,让他们有信心掌握它们,而不是认为它 difficult/not 值得。