C中文件输入的模式识别
Pattern Recognition for File input in C
我试图使用 scanf 从 C 语言的文件中获取输入。文件中的数据如下:
223234 <Justin> <Riverside>
这是我试过的以下正则表达式:
FILE* fid;
int id;
char name[100], city[100];
char dontcare1[40], dontcare3[40];
char dontcare2,dontcare4[40],dontcare5;
fid = fopen("test.txt", "r");
fscanf(fid,"%d%[^<]%c%[^<]%c%[>]%c ",&id,&dontcare1[0],
&dontcare2,&dontcare3[0],&dontcare4[0],
&city[0],&dontcare5);
我想知道是否有更好的方法来做到这一点,我将如何在不创建额外变量的情况下解释文件中的空格,这似乎无法获取括号中的城市名称。
在 *scanf()
中,您可以使用文字字符,一个 space 可以匹配多个分隔符。
为了避免处理文件,我的示例使用 sscanf()
进行了简化,但它与 fscanf()
的工作方式相同。
这里的技巧是使用 %n
来获取到该点为止的读取字符数;这样,我们确保最后一个 >
字面量确实已被读取
(我们无法通过 *scanf()
的结果得知)
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <stdio.h>
int
main(void)
{
const char *line="223234 <Justin> <Riverside>";
int id;
char name[100], city[100];
int n_read=-1;
sscanf(line, "%d <%[^>]> <%[^>]>%n",
&id, name, city, &n_read);
if(n_read!=-1) // read till the end then updated
{
printf("id=%d\n", id);
printf("name=%s\n", name);
printf("city=%s\n", city);
}
return 0;
}
尝试打开文件时,确保文件实际打开成功很有用。
FILE *fid;
fid = fopen("path/to/file", "r");
if (fid == NULL){
printf("Unable to open file. \n");
return -1;
}
实际上解决你的问题,我可能只是使用 string.h
的 strtok
函数,然后使用 space 作为分隔符。
此外,我不会使用 scanf
,而是 fgets
... 可以在各种其他 SO 文章中找到其原因。以下是未经测试的解决方案。
char line[100], line_parse[100]; // Buffer(s) to store lines upto 100
char *ret; // token used for strtok
// Read an integer and store read status in success.
while (fgets(line, sizeof(line), fPtrIn) != NULL)
{
// Copy the line for parsing, as strtok changes original string
strcpy(line_parse, line);
// Separate the line into tokens
ret = strtok(line_parse, " ");
while (ret != NULL)
{/*do something with current field*/
ret = strtok(NULL, " "); // Move onto next field
}
请注意 strtok
不是 线程安全的 。因此,在多线程代码中,您不应使用此函数。不幸的是,ISO C 标准本身不提供该函数的线程安全版本。但是许多平台都提供了这样一个功能作为扩展:在 POSIX 兼容的平台上(例如 Linux),您可以使用 strtok_r
. On Microsoft Windows, you can use the function strtok_s
功能。这两个函数都是线程安全的。
如果非要用scanf()
,其他的回答似乎面面俱到。这是一种替代方法,使用 fgetc()
和 strcpy()
.
逐字符输入
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX_SIZE 100
int main(void)
{
int id = 0, c = 0;
char buff1[MAX_SIZE], buff2[MAX_SIZE];
size_t i = 0U;
FILE *fptr = NULL;
if (!(fptr = fopen("test.txt", "r")))
{
perror("error opening file");
return -1;
}
while ((c = fgetc(fptr)) != EOF)
{
if (isdigit(c)) /* maybe check INT_MAX here if you are planning to scan big numbers */
{
id = (id * 10) + (c - '0');
}
if (i != 0 && c == ' ')
{
buff2[i] = '[=10=]';
strcpy(buff1, buff2);
i = 0U;
}
if (isalpha(c))
{
if (i < MAX_SIZE - 1)
{
buff2[i++] = c;
}
else
{
fputs("Buff full", stderr);
return -1;
}
}
}
buff2[i] = '[=10=]';
return 0;
}
您实际上可以非常简单地通过将行读入数组(缓冲区)然后从带有 sscanf()
的行中解析您需要的内容来完成此操作。不要直接使用 scanf()
,因为这会让您面临一系列与输入流中未读字符相关的陷阱。相反,通过一次读取一行来完成所有输入,然后使用 sscanf()
解析缓冲区中的值,就像使用 scanf()
一样,但是通过使用 fgets()
来读取,您一次消耗一整行,输入流中剩余的内容不取决于转换的成功或失败。
例如,您可以这样做:
#include <stdio.h>
#define MAXC 1024
#define NCMAX 100
int main (void) {
char buf[MAXC],
name[NCMAX],
city[NCMAX];
unsigned n;
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%u <%99[^>]> <%99[^>]>", &n, name, city) != 3) {
fputs ("error: invalid format", stderr);
return 1;
}
printf ("no. : %u\nname : %s\ncity : %s\n", n, name, city);
}
sscanf()
格式字符串是关键。 "%u <%99[^>]> <%99[^>]>"
将数字作为无符号值读取,<%99[^>]>
消耗 '<'
然后字符 class %99[^>]
使用 field-width 99
的修饰符来保护你的数组边界,class [^>]
将读取所有不包括 >
的字符(它对 city
下一个)。通过 检查Return[=44,验证 =] 以确保发生三个有效转换。如果不是,则处理错误。
例子Use/Output
根据您在文件 dat/no_name_place.txt
中的输入,该文件被简单地重定向到 stdin
并由程序读取,结果为:
$ ./bin/no_name_city < dat/no_name_place.txt
no. : 223234
name : Justin
city : Riverside
我试图使用 scanf 从 C 语言的文件中获取输入。文件中的数据如下:
223234 <Justin> <Riverside>
这是我试过的以下正则表达式:
FILE* fid;
int id;
char name[100], city[100];
char dontcare1[40], dontcare3[40];
char dontcare2,dontcare4[40],dontcare5;
fid = fopen("test.txt", "r");
fscanf(fid,"%d%[^<]%c%[^<]%c%[>]%c ",&id,&dontcare1[0],
&dontcare2,&dontcare3[0],&dontcare4[0],
&city[0],&dontcare5);
我想知道是否有更好的方法来做到这一点,我将如何在不创建额外变量的情况下解释文件中的空格,这似乎无法获取括号中的城市名称。
在 *scanf()
中,您可以使用文字字符,一个 space 可以匹配多个分隔符。
为了避免处理文件,我的示例使用 sscanf()
进行了简化,但它与 fscanf()
的工作方式相同。
这里的技巧是使用 %n
来获取到该点为止的读取字符数;这样,我们确保最后一个 >
字面量确实已被读取
(我们无法通过 *scanf()
的结果得知)
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <stdio.h>
int
main(void)
{
const char *line="223234 <Justin> <Riverside>";
int id;
char name[100], city[100];
int n_read=-1;
sscanf(line, "%d <%[^>]> <%[^>]>%n",
&id, name, city, &n_read);
if(n_read!=-1) // read till the end then updated
{
printf("id=%d\n", id);
printf("name=%s\n", name);
printf("city=%s\n", city);
}
return 0;
}
尝试打开文件时,确保文件实际打开成功很有用。
FILE *fid;
fid = fopen("path/to/file", "r");
if (fid == NULL){
printf("Unable to open file. \n");
return -1;
}
实际上解决你的问题,我可能只是使用 string.h
的 strtok
函数,然后使用 space 作为分隔符。
此外,我不会使用 scanf
,而是 fgets
... 可以在各种其他 SO 文章中找到其原因。以下是未经测试的解决方案。
char line[100], line_parse[100]; // Buffer(s) to store lines upto 100
char *ret; // token used for strtok
// Read an integer and store read status in success.
while (fgets(line, sizeof(line), fPtrIn) != NULL)
{
// Copy the line for parsing, as strtok changes original string
strcpy(line_parse, line);
// Separate the line into tokens
ret = strtok(line_parse, " ");
while (ret != NULL)
{/*do something with current field*/
ret = strtok(NULL, " "); // Move onto next field
}
请注意 strtok
不是 线程安全的 。因此,在多线程代码中,您不应使用此函数。不幸的是,ISO C 标准本身不提供该函数的线程安全版本。但是许多平台都提供了这样一个功能作为扩展:在 POSIX 兼容的平台上(例如 Linux),您可以使用 strtok_r
. On Microsoft Windows, you can use the function strtok_s
功能。这两个函数都是线程安全的。
如果非要用scanf()
,其他的回答似乎面面俱到。这是一种替代方法,使用 fgetc()
和 strcpy()
.
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX_SIZE 100
int main(void)
{
int id = 0, c = 0;
char buff1[MAX_SIZE], buff2[MAX_SIZE];
size_t i = 0U;
FILE *fptr = NULL;
if (!(fptr = fopen("test.txt", "r")))
{
perror("error opening file");
return -1;
}
while ((c = fgetc(fptr)) != EOF)
{
if (isdigit(c)) /* maybe check INT_MAX here if you are planning to scan big numbers */
{
id = (id * 10) + (c - '0');
}
if (i != 0 && c == ' ')
{
buff2[i] = '[=10=]';
strcpy(buff1, buff2);
i = 0U;
}
if (isalpha(c))
{
if (i < MAX_SIZE - 1)
{
buff2[i++] = c;
}
else
{
fputs("Buff full", stderr);
return -1;
}
}
}
buff2[i] = '[=10=]';
return 0;
}
您实际上可以非常简单地通过将行读入数组(缓冲区)然后从带有 sscanf()
的行中解析您需要的内容来完成此操作。不要直接使用 scanf()
,因为这会让您面临一系列与输入流中未读字符相关的陷阱。相反,通过一次读取一行来完成所有输入,然后使用 sscanf()
解析缓冲区中的值,就像使用 scanf()
一样,但是通过使用 fgets()
来读取,您一次消耗一整行,输入流中剩余的内容不取决于转换的成功或失败。
例如,您可以这样做:
#include <stdio.h>
#define MAXC 1024
#define NCMAX 100
int main (void) {
char buf[MAXC],
name[NCMAX],
city[NCMAX];
unsigned n;
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%u <%99[^>]> <%99[^>]>", &n, name, city) != 3) {
fputs ("error: invalid format", stderr);
return 1;
}
printf ("no. : %u\nname : %s\ncity : %s\n", n, name, city);
}
sscanf()
格式字符串是关键。 "%u <%99[^>]> <%99[^>]>"
将数字作为无符号值读取,<%99[^>]>
消耗 '<'
然后字符 class %99[^>]
使用 field-width 99
的修饰符来保护你的数组边界,class [^>]
将读取所有不包括 >
的字符(它对 city
下一个)。通过 检查Return[=44,验证 =] 以确保发生三个有效转换。如果不是,则处理错误。
例子Use/Output
根据您在文件 dat/no_name_place.txt
中的输入,该文件被简单地重定向到 stdin
并由程序读取,结果为:
$ ./bin/no_name_city < dat/no_name_place.txt
no. : 223234
name : Justin
city : Riverside