【问题标题】:Using sscanf to read from a file使用 sscanf 从文件中读取
【发布时间】:2014-11-22 02:09:37
【问题描述】:

当文件包含按以下方式格式化的行时,我如何从文件中读取行:

<string> "<string>"

第一个字符串不能有空格。引号内的字符串可以有空格。

在将文件读入chararray后,我尝试了以下形式的东西

sscanf(buffer, "%s", string1);
sscanf(buffer, "%*s \"%[^\"]\"", string2);

问题是我在某些行中得到了一些奇怪的字符。大多数字符都很好,但对于长字符串,我会得到一些不应该出现的字符。

编辑:

看起来这个问题可能与sscanf 无关。当我在sscanf 之后立即打印string1string2 时,我得到了正确的输出。我的程序设置方式我必须malloc 新内存并将结果复制到char*。正是当我打印后一个结果时,才会出现奇怪的行为。我使用char *cpystring1 = malloc(strlen(string1)),然后使用memcpy(cpystring1, string1, strlen(string1)。这一直对我有用,但是,也许这不是最好的方法?

问题已解决:

详情请看我留下的最后一条评论。

【问题讨论】:

  • 您确定要在大量缓冲区之间复制字符串吗?你知道你可以用strtok() 和朋友进行标记,对吧?
  • string1string2 有足够的存储空间吗?
  • @BLUEPIXY。是的。
  • 请注意,malloc(strlen(string1)) 没有为终止的 null 分配空间。它必须是malloc(strlen(string1)+1),或者(更好)使用strdup()。还要注意,您的 memcpy() 不会终止您复制的字符串。
  • @JonathanLeffler。谢谢我发现了问题。我最初分配了添加 +1 的空间,但是在我收到 fgets 的输入和 memcpy'd 之后,我都以空值终止。添加 +1 和 null 终止单次解决了这个问题。

标签: c string fgets scanf


【解决方案1】:

正如BLUEPIXYcomment 中所说,一个问题是“你有足够的空间来存储字符串吗?”确实,您的变量是char string1[SOME_SIZE]; 还是char *string1;,如果是后者,您如何为它们分配空间?奇怪的多余字符等症状通常是由于正在读取的数据空间不足造成的。

您可以将这两个操作合并为一个。您还应该限制阅读的大小;你应该检查你的阅读是否成功。假设你有两个大小为 128 的数组,你可以写

char string1[128];
char string2[128];

if (sscanf(buffer, "%127s \"%127[^\"]\"", string1, string2) != 2)
    …format error…

注意,格式字符串中指定的数字是字符串的最大长度不包括空字节。这与库的其他部分并不完全一致,但早在 C 标准出现之前就已建立实践,并且更改规则所破坏的内容比修复的要多。

还要注意,使用上面的代码,您永远不会知道尾随双引号是否存在。如果您也需要这些信息,则必须更加努力:

int  offset = 0;
char string1[128];
char string2[128];

if (sscanf(buffer, "%127s \"%127[^\"]\"%n", string1, string2, &offset) != 2 || offset == 0)
    …format error…

最后,如果您在 POSIX 系统上,%s%c%[…] 操作的 m 修饰符会动态地为读取的数据分配正确的空间量(但您通过指向 char * 的指针,以供转换规范使用,而不是普通的 char *):

int  offset = 0;
char *tag = 0;
char *value = 0;

if (sscanf(buffer, "%ms \"%m[^\"]\"%n", &tag, &value, &offset) != 2 || offset == 0)
    …format error…

请注意,某些系统(例如 Mac OS X 10.10.1 Yosemite)不支持 m 修饰符,即使它是 sscanf() 的 POSIX 2008 规范的一部分。 GNU C 库(例如在 Linux 上找到)确实支持它。

值得思考sscanf() 是否是处理这些数据的最佳方式。可以使用它:我刚刚展示了如何使用(当你写这个问题时,你已经拥有了大部分你需要的东西)。然而,准确使用它是一个棘手的功能——这就是礼貌。 sscanf() 的好处在于,您可以实现“如果一开始没有成功,请尝试、尝试、再试”。使用像scanf() 这样的直接I/O 功能,一般情况下您不能再试一次。但是,您也可以使用字符串操作来获得您想要的输出,尽管这样做的简单方法会破坏输入字符串:

char *tag = buffer + strspn(buffer, " \t");  // Skip leading blanks and tabs
char *eot = tag + strcspn(tag, " \t");    // Tag does not contain blanks or tabs
if (*eot == '\0')
    …report format error (no value after tag); do not continue…
*eot++ = '\0';                               // Null terminate the tag
char *value = eot + strspn(eot, " \t");   // Skip separating blanks and tabs
if (*value++ != '"')
    …report format error (missing open double quote); do not continue…
char *eov = strchr(value, '"');
if (eov == 0)
    …report format error (missing close double quote); do not continue…
*eov = '\0';

还有其他方法可以进行该扫描,但 strspn()strcspn() 是 C89 和所有更高版本的标准中的标准函数。

工作测试代码

使用 GCC 4.8.2 在 Ubuntu 14.04 LTS 上测试。

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

static void parsing(char *buffer)
{
    char *tag = buffer + strspn(buffer, " \t");     // Skip leading blanks and tabs
    char *eot = tag + strcspn(tag, " \t");     // Tag does not contain blanks or tabs
    if (*eot == '\0')
    {
        printf("Got tag <<%s>> but no following data\n", tag);
        return;
    }
    *eot++ = '\0';                       // Null terminate the tag
    char *value = eot + strspn(eot, " \t");     // Skip separating blanks and tabs
    if (*value++ != '"')
    {
        printf("Got tag <<%s>> but it is not followed by a double-quoted string\n", tag);
        return;
    }
    char *eov = strchr(value, '"');
    if (eov == 0)
    {
        printf("Got tag <<%s>> and value <<%s>> but it is not followed by a double-quote\n", tag, value);
        return;
    }
    *eov = '\0';

    printf("<<%s>> <<%s>>\n", tag, value);
}

int main(void)
{
    char buffer[] = " \ttagname \" corresponding tag \tvalue is OK \"  ";

    {
        int offset = 0;
        char string1[128];
        char string2[128];

        if (sscanf(buffer, "%127s \"%127[^\"]\"%n", string1, string2, &offset) != 2 || offset == 0)
            printf("sscanf() 1 failed\n");
        else
            printf("<<%s>> <<%s>>\n", string1, string2);
    }

    {
        int offset = 0;
        char *tag = 0;
        char *value = 0;

        if (sscanf(buffer, "%ms \"%m[^\"]\"%n", &tag, &value, &offset) != 2 || offset == 0)
            printf("sscanf() 2 failed\n");
        else
            printf("<<%s>> <<%s>>\n", tag, value);

        free(tag);
        free(value);
    }

    parsing(buffer);

    return 0;
}

输出:

<<tagname>> << corresponding tag    value is OK >>
<<tagname>> << corresponding tag    value is OK >>
<<tagname>> << corresponding tag    value is OK >>

【讨论】:

    【解决方案2】:

    使用strchr 查找输入缓冲区中第一次出现的空格字符:

    char *space1 = strchr(buffer, ' ');
    *space1 = '\0'; /* terminate the first string in place */
    space1++; /* move pointer to beginning of string2 */
    
    printf("String1 = \"%s\"\n", buffer);
    printf("String2 = \"%s\"\n", space1);
    

    注意:上面的代码不包含安全检查,它只是为了说明这个想法。我也没有包括摆脱第二个字符串周围的" 字符的逻辑,但这应该很容易。提示:将space1 增加2,并将最后一个" 替换为\0

    【讨论】:

    • @Jonathan Leffler:您对前导空格是正确的,但 OP 不需要它。关于双引号:我明确表示我没有为此烦恼:-),但请参阅我的最后两句话。回复:格式错误:有一个“不包含安全检查”的免责声明。 :-) OP 同时意识到他的问题与sscanf 无关;-)))
    • 好的;很公平。让我们删除所有这些 cmets。
    猜你喜欢
    • 1970-01-01
    • 2019-07-25
    • 1970-01-01
    • 2016-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    相关资源
    最近更新 更多