使用 fscanf,将文件扫描到 C 中的结构中,但第一个参数已经失败

Posted

技术标签:

【中文标题】使用 fscanf,将文件扫描到 C 中的结构中,但第一个参数已经失败【英文标题】:Using fscanf, scanning a file into a struct in C, but the first argument is failing already 【发布时间】:2022-01-15 18:29:57 【问题描述】:

我有一个文件,我试图将每一行读入 C 中的结构以进一步使用它。

文件如下所示:

Bread,212,2.7,36,6,9.8,0.01,0.01,10,500 
Pasta,347,2.5,64,13,7,0.01,0.01,6,500 
Honey,340,0.01,83,0.01,0.01,0.01,0.01,22.7,425 
Olive-oil,824,92,0.01,0.01,0.01,0.01,13.8,35,500 
White-beans,320,2.7,44,21,18,0.01,0.01,11,400 
Flaxseed-oil,828,92,0.01,0.01,0.01,52,14,100,100 
Cereal,363,6.5,58,13,9.9,0.01,0.01,11,1000 
Hazelnuts,644,61.6,10.5,12,0.01,0.09,7.83,16.74,252 

所以我写了一个for-loop 来遍历文件中的行,试图将每个值存储到struct 的字段中。我尝试打印结构的字段,但第一个参数字符串已经出错了。

正在打印:

scanresult: 1, name:  ■B, kcal: 0.00, omega 3: 0.00, omega 6: 0.00, carb: 0.00, protein: 0.00, fib: 0.00, price: 0.00, weight: 0.00g

Scanres 应该是10,而不是1,并且值应该与文件第一行的值匹配。

我尝试过在格式化字符串的参数前面使用或不使用空格。我还尝试了编译器警告-Wall-pedantic。未发现任何问题。

还有什么可能导致这个问题?

代码如下所示:

#include <stdio.h>

#define MAX_CHAR 100
#define SIZE_OF_SHELF 8

typedef struct 
    char name[MAX_CHAR];
    double kcal, fat, omega_3, omega_6, carb, protein, fib, price, weight;
 Food;

int main(void) 
    int i = 0, scanresult;
    Food Shelf[SIZE_OF_SHELF];
    FILE *fp;

    fp = fopen("foods.txt", "r");

    if (! fp) 
        printf("error loading file. bye.\n");
        return 0;
    

    for (i = 0; !feof(fp); i++) 
        scanres = fscanf(fp, " %[^,],%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf ",
                         Shelf[i].name,
                         &Shelf[i].kcal, &Shelf[i].fat,
                         &Shelf[i].carb, &Shelf[i].protein,
                         &Shelf[i].fib, &Shelf[i].omega_3,
                         &Shelf[i].omega_6, &Shelf[i].price,
                         &Shelf[i].weight);
        
        printf("scanres: %d, name: %s, kcal: %.2f, omega 3: %.2f, omega 6: %.2f, carb: %.2f, protein: %.2f, fib: %.2f, price: %.2f, weight: %.2fg\n",
               scanres, Shelf[i].name, Shelf[i].kcal,
               Shelf[i].omega_3, Shelf[i].omega_6, Shelf[i].carb, 
               Shelf[i].protein, Shelf[i].fib, Shelf[i].price,
               Shelf[i].weight);
    
    return 0;

如果有人发现我做错了什么,请告诉我。

【问题讨论】:

你用什么编辑器来制作你的文本文件? txt 文件可能使用每个字符使用多个字节的编码保存,而 fscanf 不接受它。我会先尝试从控制台读取,然后复制粘贴这些行以查看 scanf 是否读取它们。 输入文件中可能存在字节顺序标记 (BOM)。十六进制转储文件,并检查前三个字符。 [回答:你可以使用 fgets() 读取整行,然后解析这些行, 做所有基于字符的事情(这对于浮点数来说很难)] 文件实际上是问题所在!我从头开始创建了一个新文件,它可以工作。我认为这是因为该文件源自 csv 并保存为文本文件。谢谢你们的帮助!祝您有美好的一天。 另见Why is “while ( !feof (file) )” always wrong? @mcklmo 我还应该使用什么? 尽管有很多失败之处,fscanf 几乎不能胜任这项任务。但是,当您准备好超越它时,请参阅What can I use for input conversion instead of scanf? 【参考方案1】:

检查文件的前三个字符中是否有字节顺序标记 (BOM)。您可以使用hexdump(或任何二进制编辑器)对其进行检查。

文件带有 BOM:


00000000  ef bb bf 42 72 65 61 64  2c 32 31 32 2c 32 2e 37  |...Bread,212,2.7|
00000010  2c 33 36 2c 36 2c 39 2e  38 2c 30 2e 30 31 2c 30  |,36,6,9.8,0.01,0|
00000020  2e 30 31 2c 31 30 2c 35  30 30 20 0a 50 61 73 74  |.01,10,500 .Past|
00000030  61 2c 33 34 37 2c 32 2e  35 2c 36 34 2c 31 33 2c  |a,347,2.5,64,13,|
...

文件没有 BOM:


00000000  42 72 65 61 64 2c 32 31  32 2c 32 2e 37 2c 33 36  |Bread,212,2.7,36|
00000010  2c 36 2c 39 2e 38 2c 30  2e 30 31 2c 30 2e 30 31  |,6,9.8,0.01,0.01|
00000020  2c 31 30 2c 35 30 30 20  0a 50 61 73 74 61 2c 33  |,10,500 .Pasta,3|
00000030  34 37 2c 32 2e 35 2c 36  34 2c 31 33 2c 37 2c 30  |47,2.5,64,13,7,0|
...

【讨论】:

但是为什么BOM会导致代码失败? 我不知道。也许fscanf() 实现不是 8 位干净的? 实际上,我可以或多或少地通过给foods.txt 一个BOM,但将其编码为UTF-16(小端)而不是UTF-8 来实现相同的失败。 (我想一切皆有可能,但我从未听说过 fscanf 的“非 8 位干净”版本!) :-)] 无论如何,这是一个非常好的 BOM 调用,我将不得不记住这个线程。我们每天都会收到关于看似精美的代码却无法读取普通文本文件的问题,而且“错误的 Unicode 编码”从未出现在我的可能性菜单中。 我的情况更糟。我猜 mcklmo 从 Microsoft Excel 中保存了该 CSV 文件。 Microsoft 喜欢 UTF-16,他们甚至喜欢在 UTF-8 中使用 BOM(当然,在这种情况下,它们是相当可笑的)。我发现如果你从 Excel 中以 UTF-8 保存 TDF 文件,如果第一个单元格是空的,这意味着文件的前四个字节是 BOM 后跟一个 TAB,如果你 cat 这个文件在 MacOS 终端窗口中——它使终端崩溃!【参考方案2】:

除了Byte Order Mark (BOM) 之外,foods.txt 文件的原始副本很可能是使用UTF-16 编码的,而不是使用 ASCII 或更流行和兼容的UTF-8。从 wildplasser 的回答中得到提示,这是该编码的 little-endian 变体中文件第一部分的 hex dump:

00000000  ff fe 42 00 72 00 65 00  61 00 64 00 2c 00 32 00  |..B.r.e.a.d.,.2.|
00000010  31 00 32 00 2c 00 32 00  2e 00 37 00 2c 00 33 00  |1.2.,.2...7.,.3.|
00000020  36 00 2c 00 36 00 2c 00  39 00 2e 00 38 00 2c 00  |6.,.6.,.9...8.,.|
00000030  30 00 2e 00 30 00 31 00  2c 00 30 00 2e 00 30 00  |0...0.1.,.0...0.|
00000040  31 00 2c 00 31 00 30 00  2c 00 35 00 30 00 30 00  |1.,.1.0.,.5.0.0.|
00000050  20 00 0a 00 50 00 61 00  73 00 74 00 61 00 2c 00  | ...P.a.s.t.a.,.|
00000060  33 00 34 00 37 00 2c 00  32 00 2e 00 35 00 2c 00  |3.4.7.,.2...5.,.|

前导的ff fe 代表字节顺序标记,并且会解释出现在输出name: ■B 中的神秘。此后,每隔一个字节为 0,这就是“Bread”被截断为“B”的原因。然后 fscanf 的第一个 %lf 看到“r\0e\0a\0d”,无法将其解析为双精度数,这就是为什么 fscanf 返回 1 而不是 10。

【讨论】:

这也解释了为什么kcalfatomega_3omega_6carbproteinfibprice、@987654342没有一个预期值:scanf() 在第一次转换后停止转换并保持不变,因此它们的值不确定,因为 Shelf 数组未初始化。【参考方案3】:

将 .txt 文件的内容复制到一个新的 .txt 文件中解决了这个问题。它起源于一个 .xls 文件,我的猜测是,你们中的一些人提到的奇怪的 BOM 内容来自那里。

【讨论】:

以上是关于使用 fscanf,将文件扫描到 C 中的结构中,但第一个参数已经失败的主要内容,如果未能解决你的问题,请参考以下文章

C fscanf 输入验证

如何从c中的txt文件正确扫描内容

C语言 怎么把文件中的信息储存到结构体数组中

C中的fscanf - 如何确定逗号?

C语言问题:下面是结构体和一个子函数,单步执行到 fscanf 时,程序直接跳到了下一行,fscanf 不执行!求教

fscanf()函数。使用C中的模式读取文件