从文件的多行读取 - 在 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
的每一行并在每个符合条件的行中存储内存地址和它的大小(例如,322334bba0a
和 4
) .
// 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]
、addr
和 len
的解析值,因此您可以轻松地将其替换为任何 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 值得。
我想在 Linux 命令行中以类似流的方式从日志文件中读取,日志文件如下所示:
=== Start ===
I 322334bbaff, 4
I 322334bba0a, 4
S ff233400ff, 8
I 000004bbaff, 4
L 322334bba0a, 4
=== End ===
并且我有一个 c 文件来读取 log file
的每一行并在每个符合条件的行中存储内存地址和它的大小(例如,322334bba0a
和 4
) .
// 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
.
./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]
、addr
和 len
的解析值,因此您可以轻松地将其替换为任何 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 值得。