以空格作为分隔符的fscanf - 我应该使用什么格式?

时间:2017-06-30 18:46:44

标签: c

我有一个txt文件,其行如下

[7 chars string][whitespace][5 chars string][whitespace][integer]

我想使用fscanf()将所有这些内容读入内存,我对应该使用的格式感到困惑。

以下是此类行的示例:

hello   box   94324

注意每个字符串中的填充空格,除了分隔的空格。

编辑:我知道首先使用fgets()的建议,我不能在这里使用它。

编辑:这是我的代码

typedef struct Product {
    char* id;   //Product ID number. This is the key of the search tree.
    char* productName;  //Name of the product.
    int currentQuantity;    //How many items are there in stock, currently. 
} Product;

int main()
{
    FILE *initial_inventory_file = NULL;
    Product product = { NULL, NULL, 0 };

    //open file 
    initial_inventory_file = fopen(INITIAL_INVENTORY_FILE_NAME, "r");

    product.id = malloc(sizeof(char) * 10); //- Product ID: 9 digits exactly. (10 for null character)
    product.productName = malloc(sizeof(char) * 11); //- Product name: 10 chars exactly.

    //go through each line in inital inventory
    while (fscanf(initial_inventory_file, "%9c %10c %i", product.id, product.productName, &product.currentQuantity) != EOF)
    {
        printf("%9c %10c %i\n", product.id, product.productName, product.currentQuantity);
    }

    //cleanup...
    ...
}

这是一个文件示例:(它实际上是10个字符,9个字符和int)

022456789 box-large  1234
023356789 cart-small 1234
023456789 box        1234
985477321 dog food   2
987644421 cat food   5555
987654320 snaks      4444
987654321 crate      9999
987654322 pillows    44

4 个答案:

答案 0 :(得分:3)

假设您的输入文件格式正确,这是最直接的版本:

char str1[8] = {0};
char str2[6] = {0};
int  val;
...
int result = fscanf( input, "%7s %5s %d", str1, str2, &val );

如果result等于3,则表示您已成功读取所有三个输入。如果它小于3但不是EOF,那么您的一个或多个输入上的匹配失败。如果它是EOF,你要么点击文件的末尾就会出现输入错误;在此时使用feof( input )来测试EOF。

如果无法保证您的输入文件格式正确(我们大多数人都不能),那么您最好在整行中阅读文本并自行解析。您说您无法使用fgets,但有一种方法可以使用fscanf

char buffer[128]; // or whatever size you think would be appropriate to read a line at a time

/**
 * " %127[^\n]" tells scanf to skip over leading whitespace, then read
 * up to 127 characters or until it sees a newline character, whichever
 * comes first; the newline character is left in the input stream.
 */
if ( fscanf( input, " %127[^\n]", buffer ) == 1 )
{
  // process buffer
}

然后,您可以使用sscanf解析输入缓冲区:

int result = sscanf( buffer, "%7s %5s %d", str1, str2, &val );
if ( result == 3 )
{
  // process inputs
}
else
{
  // handle input error
}

或通过其他方法。

修改

需要注意的边缘情况:

  1. 每行缺少一个或多个输入
  2. 格式错误的输入(例如整数字段中的非数字文本)
  3. 每行不止一组输入
  4. 超过7或5个字符的字符串
  5. 价值太大,无法存储在int
  6. 编辑2

    我们大多数人不推荐fscanf的原因是因为它有时会使错误检测和恢复变得困难。例如,假设您有输入记录

    foo     bar    123r4
    blurga  blah   5678
    

    并且您使用fscanf( input, "%7s %5s %d", str1, str2, &val );阅读。 fscanf会读取123并将其分配给val,并在输入流中保留r4。在下一个电话中,r4将被分配到str1blurga将被分配到str2,您将在blah上获得匹配的失败。理想情况下,你想要拒绝整个第一条记录,但是当你知道有问题时,为时已晚。

    如果您将其作为字符串首先读取,您可以解析并检查每个字段,如果其中任何一个字段不好,您可以拒绝整个字段。

答案 1 :(得分:1)

使用"%9c ..."格式的代码中的问题是%9c不会写字符串终止字符。因此,您的字符串可能充满了垃圾而根本没有终止,这会导致使用printf打印时出现未定义的行为。

如果在第一次扫描之前将字符串的完整内容设置为0,它应该按预期工作。为此,您可以使用calloc代替malloc;这将使用0初始化内存。

请注意,代码还必须以某种方式使用换行符,该换行符由另外的fscanf(f,"%*c")语句解决(*表示该值已被使用,但未存储到变量中) 。只有在最后一个数字和换行符之间没有其他空格时才会起作用:

int main()
{
    FILE *initial_inventory_file = NULL;
    Product product = { NULL, NULL, 0 };

    //open file
    initial_inventory_file = fopen(INITIAL_INVENTORY_FILE_NAME, "r");

    product.id = calloc(sizeof(char), 10); //- Product ID: 9 digits exactly. (10 for null character)
    product.productName = calloc(sizeof(char), 11); //- Product name: 10 chars exactly.

    //go through each line in inital inventory
    while (fscanf(initial_inventory_file, "%9c %10c %i", product.id, product.productName, &product.currentQuantity) == 3)
    {
        printf("%9s %10s %i\n", product.id, product.productName, product.currentQuantity);
        fscanf(initial_inventory_file,"%*c");
    }

    //cleanup...
}

答案 2 :(得分:1)

我们假设输入是

<LWS>* <first> <LWS>+ <second> <LWS>+ <integer>

其中<LWS>是任何空格字符,包括换行符; <first>有一到七个非空白字符; <second>有一到五个非wihitespace字符; <integer>是一个可选的有符号整数(如果以0x0X开头,则为十六进制,如果以0开头,则为八进制,否则为十进制); *表示前面元素中的零个或多个; +表示前面一个或多个元素。

假设你有一个结构,

struct record {
    char first[8];  /* 7 characters + end-of-string '\0' */
    char second[6]; /* 5 characters + end-of-string '\0' */
    int  number;
};

然后你可以使用例如将来自流in的下一条记录读入调用者指向的结构。

#include <stdlib.h>
#include <stdio.h>

/* Read a record from stream 'in' into *'rec'.
   Returns: 0 if success
           -1 if invalid parameters
           -2 if read error
           -3 if non-conforming format
           -4 if bug in function
           +1 if end of stream (and no data read)
*/
int read_record(FILE *in, struct record *rec)
{
    int rc;

    /* Invalid parameters? */
    if (!in || !rec)
        return -1;

    /* Try scanning the record. */
    rc = fscanf(in, " %7s %5s %d", rec->first, rec->second, &(rec->number));

    /* All three fields converted correctly? */
    if (rc == 3)
        return 0; /* Success! */

    /* Only partially converted? */
    if (rc > 0)
        return -3;

    /* Read error? */
    if (ferror(in))
        return -2;

    /* End of input encountered? */
    if (feof(in))
        return +1;

    /* Must be a bug somewhere above. */
    return -4;
}

转换说明符%7s最多可转换七个非空白字符,%5s最多可转换为五个;数组(或字符指针)必须有一个额外的字符串结尾字节'\0'scanf() family of functions自动添加。{/ p>

如果未指定长度限制,并使用%s,则输入可能会超出指定的缓冲区。这是常见buffer overflow错误的常见原因。

scanf()系列函数的返回值是成功转换的次数(可能为0),如果发生错误,则为EOF。在上面,我们需要三次转换才能完全扫描记录。如果我们只扫描1或2,我们有一个部分记录。否则,我们通过检查ferror()来检查是否发生了流错误。 (请注意,您要在ferror()之前检查feof(),因为错误情况也可能设置为feof()。)如果没有,我们检查扫描功能是否在任何事情之前遇到了流末尾已转换,使用feof()

如果上述情况均未得到满足,则扫描函数返回零或负数,而ferror()feof()都不返回true。因为扫描模式以(空格和)转换说明符开头,所以它永远不会返回零。 scanf()系列函数中唯一的非正返回值是EOF,这会导致feof()返回true。因此,如果上述情况都不符合,则代码中必定存在错误,由输入中的一些奇怪的案例触发。

将某些流中的结构读入动态分配的缓冲区的程序通常会实现以下伪代码:

Set ptr = NULL  # Dynamically allocated array
Set num = 0     # Number of entries in array
Set max = 0     # Number of entries allocated for in array

Loop:

    If (num >= max):
        Calculate new max; num + 1 or larger
        Reallocate ptr
        If reallocation failed:
            Report out of memory
            Abort program
        End if
    End if

    rc = read_record(stream, ptr + num)
    If rc == 1:
        Break out of loop
    Else if rc != 0:
        Report error (based on rc)
        Abort program
    End if
End Loop

答案 3 :(得分:0)

您是否尝试过格式说明符?

char seven[8] = {0};
char five[6] = {0};
int myInt = 0;

// loop here
fscanf(fp, "%s %s %d", seven, five, &myInt);
// save to structure / do whatever you want

如果您确定格式和字符串是始终固定的长度,您还可以逐个字符地迭代输入(使用类似fgetc()的内容并手动处理它。上面的示例可能会导致细分如果文件中的字符串超过5或7个字符,则会出错。

编辑手动扫描循环:

char seven[8] = {0};
char five[6] = {0};
int myInt = 0;

// loop this part
for (int i = 0; i < 7; i++) {
    seven[i] = fgetc(fp);
}
assert(fgetc(fp) == ' '); // consume space (could also use without assert)
for (int i = 0; i < 5; i++) {
    five[i] = fgetc(fp);
}
assert(fgetc(fp) == ' '); // consume space (could also use without assert)
fscanf(fp, "%d", &myInt);