fscanf()读取格式化行中带有空格的字符串

6
使用这个结构:
typedef struct sProduct{
  int code;
  char description[40];
  int price;
};

我想要读取一个具有以下格式的txt文件:

1,Vino Malbec,12

格式应为:代码,描述,价格。但是当描述中有空格时,我无法读取它。

我尝试了这个:

fscanf(file,"%d,%[^\n],%d\n",&p.code,&p.description,&p.price);

代码已经成功保存,但是描述中保存了 Vino Malbec,12,而我只想保存 Vino Malbec,因为 12 是价格。
需要帮助吗? 谢谢!

难道不应该是 %[^\n]s 吗? - nj-ath
@darknight,我已经尝试过了,结果一样。 - Mati Tucci
Try "%d ,%[^\n,],%d" - chux - Reinstate Monica
@chux 只在描述中保存单词“Vino”。 - Mati Tucci
重写 - 将"%[^\n, ]"中的' '去掉。 - chux - Reinstate Monica
@chux 不对。问题还是一样的。明确一下,我正在写:"%d,%[^\n, ],%d\n",这就是你的意思吗? - Mati Tucci
2个回答

9
主要问题在于"%[^\n],""%[^\n]"可以扫描除了'\n'之外的所有内容,所以description会扫描到','。当遇到逗号时,代码需要停止扫描description
对于基于行的文件数据,首先逐行读取。
char buf[100];
if (fgets(buf, sizeof buf, file) == NULL) Handle_EOForIOError();

然后进行扫描。使用%39[^,]来避免扫描','并将宽度限制为39个字符
int cnt = sscanf(buf,"%d , %39[^,],%d", &p.code, p.description, &p.price);
if (cnt != 3) Handle_IllFormattedData();

另一个巧妙的技巧是:使用"%n"来记录解析结束。
int n = 0;
sscanf(buf,"%d , %39[^,],%d %n", &p.code, p.description, &p.price, &n);
if (n == 0 || buf[n]) Handle_IllFormattedData_or_ExtraData();

[编辑]

简化:@user3386109

更正:@cool-guy 删除 & 符号


1
@user3386109 确实,在 "%39[^\n,]" 中包含 \n 真的不必要:可以是 "%39[^,]"。无论如何,对 fgets()sscanf() 返回值的检查将确保所有项都存在,包括第二个 ',' - chux - Reinstate Monica
1
@Sridhar 在"%39[^,]"中省略39会导致当 description 输入超过39个字符时出现问题。在sscanf()中大量使用" "通常允许适当的额外间距,因此我建议在格式中添加空格-虽然这主要是可选的。 - chux - Reinstate Monica

2

@chux已经提供了一个很好的答案,但如果你需要使用fscanf,请使用以下代码:

if(fscanf(file,"%d,%39[^,],%d",&p.code,p.description,&p.price)!=3)
    Handle_Bad_Data();

这里,fscanf首先扫描一个数字(%d)并将其放入p.code。然后,它扫描一个逗号(并丢弃它),然后扫描最多39个字符或直到逗号,并将所有内容放入p.description。然后,它扫描一个逗号(并丢弃它),然后扫描一个数字(%d)并将其放入p.price


1
使用fscanf()直接的方法很不错。fscanf()fgets()/sscanf()之间存在微妙的区别:
  1. 如果文件数据包含嵌入的空字符'\0'fscanf(..."%39[^,]"...)将会消耗这个'\0'并继续读取。而fgets()也会这样做,但是通过sscanf()更容易检测到。
  2. 当输入错误(语法错误)时,fgets()/sscanf()的方法比fscanf()更容易重新同步并继续执行。然而对于fscanf()来说,快速编码更简单一些。
- chux - Reinstate Monica
我不知道!嵌入的\0是什么意思? - Spikatrix
1
假设一个文件有一行不寻常的内容:"1,abc\0xyz,12\n"。fscanf(..."%d,%39[^,],%d"...)扫描7个字符到description中,然后附加一个终止空字符'\0',使其包含"abc"、一个空字符、"xyz"和最后一个空字符。打印description时,只会显示"abc"。使用fgets(),同样的"1,abc\0xyz,12\n"将被读入缓冲区,但是sscanf()将在第一个空字符处停止扫描,因此只能看到"1,abc",并且无法解析sscanf() - chux - Reinstate Monica
嵌入式的 '\0' 不常见,但它们偶尔会出现,并且也是黑客破坏代码的潜在恶意方式。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接