如何使用scanf / sscanf确认没有空格或尾随数据?

时间:2015-05-18 01:52:27

标签: c whitespace scanf

sscanf()似乎非常适合剥离匹配数据,例如:

sscanf ("abc,f,123,234", "%[a-z],%c,%d,%d", str, &chr, &i1, &i2)

但是我需要断言它没有遇到空格:

sscanf ("abc,  f  , 123    , 234  ", "%[a-z],%c,%d,%d", str, &chr, &i1, &i2)
/* How to tell it to fail on whitespace?? */

此外,我需要声明没有尾随数据:

sscanf ("abc,f,123,234__SOMERUBBISH", "%[a-z],%c,%d,%d", str, &chr, &i1, &i2)
/* How to detect trailing rubbish or make sscanf fail */

如何让sscanf更严格地解析字符串?

这是一个编译为ANSI C的大学作业,我没有选择包含正则表达式。

5 个答案:

答案 0 :(得分:3)

很容易就是馅饼。

更改格式以找到潜在的不需要的空白区域。使用"%n"记录缓冲区中的扫描位置。使用可选前导空格的"%d""%s""%f"之前的格式说明符。添加最终"%n"以检查尾随垃圾。

首先检查是否扫描了4个变量。然后检查是否发生了不需要的数据。

注意:只有"%[]""%c""%n"不使用可选的前导空格。

int ws[3];
int cnt = sscanf (buf, "%[a-z],%c,%n%d,%n%d%n", str, &chr, &ws[0], &i1, &ws[1], &i2, &ws[2]);
if (cnt != 4 || isspace(buf[ws[0]]) || isspace(buf[ws[1]]) || buf[ws[2]]) {
    Fail();
}

答案 1 :(得分:1)

简洁地说,如果您不能允许空格,则不能使用直接文件I / O函数,例如scanf()等。每个%d转换都允许在值之前包含任意数量的空格,包括换行符。您必须使用基于字符串的函数,例如sscanf()

您最好使用fgets()或POSIX getline()阅读数据行,然后使用%n确定转化完成的位置。

如果您没有删除fgets()getline()保存的换行符,则可以测试输入中最后一个匹配(或第一个不匹配的字符)后的第一个字符是换行符;否则,您可以测试空字节作为第一个不匹配的字符。

你仍然需要检查两个号码之前是否没有空格;你再为每一个使用%n。请注意,%n转换规范不计入scanf()等人返回的数字

ws.c

#include <stdio.h>

int main(void)
{
    char   str[10] = "QQQQQQQQQ";
    char   chr = 'Z';
    int    i1 = 77;
    int    i2 = 88;
    int    n1;
    int    n2;
    int    n3;
    char  *line = 0;
    size_t linelen = 0;
    int    length;

    while ((length = getline(&line, &linelen, stdin)) != -1)
    {
        printf("Line: <<%.*s>>\n", length - 1, line);

        int rc = sscanf(line, "%[a-z],%c,%n%d,%n%d%n",
                        str, &chr, &n1, &i1, &n2, &i2, &n3);

        const char *tag = "success";
        if (rc <= 0)
            tag = "total failure";
        else if (rc < 4)
            tag = "partial failure";
        else if (rc > 4)
            tag = "WTF?";
        printf("rc = %d: %s\n", rc, tag);
        printf("n1 = %d [%c], n2 = %d [%c], n3 = %d [%c]\n",
               n1, line[n1], n2, line[n2], n3, line[n3]);
        printf("<<%s>>,<<%c>>,%d,%d\n", str, chr, i1, i2);
    }
    return 0;
}

这样可以识别出问题所在。

data

使用☐标记行尾,请考虑数据文件(data):

abc,f,123,234☐
abc,  f  , 123    , 234  ☐
abc,f,123,234__SOMERUBBISH☐
xyz,f, 123, 234☐
xyz,f,123 ,234 ☐

运行示例

上述程序的输出是:

$ ./ws < data
Line: <<abc,f,123,234>>
rc = 4: success
n1 = 6 [1], n2 = 10 [2], n3 = 13 [
]
<<abc>>,<<f>>,123,234
Line: <<abc,  f  , 123    , 234  >>
rc = 2: partial failure
n1 = 6 [f], n2 = 10 [ ], n3 = 13 [3]
<<abc>>,<< >>,123,234
Line: <<abc,f,123,234__SOMERUBBISH>>
rc = 4: success
n1 = 6 [1], n2 = 10 [2], n3 = 13 [_]
<<abc>>,<<f>>,123,234
Line: <<xyz,f, 123, 234>>
rc = 4: success
n1 = 6 [ ], n2 = 11 [ ], n3 = 15 [
]
<<xyz>>,<<f>>,123,234
Line: <<xyz,f,123 ,234 >>
rc = 3: partial failure
n1 = 6 [1], n2 = 11 [2], n3 = 15 [
]
<<xyz>>,<<f>>,123,234
$

显然,对于标记为“部分失败”的行,您不能依赖上次成功转换之外的数据。但是,如果转换成功,您可以通过检查line[n1]等来发现问题。

ws2.c

代码的这种微小变化给出了稍微扩展的问题分析。请注意,此分析不适用于部分或完全不成功的扫描。最好是当sscanf()的返回值不是4时只报告问题,只扫描扫描成功时的值。 (这样做的修改并不复杂。)它还可以防止长字符串的缓冲区溢出作为第一个字段。

#include <ctype.h>
#include <stdio.h>

#undef isdecint
static inline int isdecint(int c)
{
    return (isdigit(c) || c == '+' || c == '-');
}

int main(void)
{
    char   str[10] = "QQQQQQQQQ";
    char   chr = 'Z';
    int    i1 = 77;
    int    i2 = 88;
    int    n1;
    int    n2;
    int    n3;
    char  *line = 0;
    size_t linelen = 0;
    int    length;

    while ((length = getline(&line, &linelen, stdin)) != -1)
    {
        printf("Line: <<%.*s>>\n", length - 1, line);

        int rc = sscanf(line, "%9[a-z],%c,%n%d,%n%d%n",
                        str, &chr, &n1, &i1, &n2, &i2, &n3);

        const char *tag = "success";
        if (rc <= 0)
            tag = "total failure";
        else if (rc < 4)
            tag = "partial failure";
        else if (rc > 4)
            tag = "WTF?";
        printf("rc = %d: %s\n", rc, tag);
        printf("n1 = %d [%c], n2 = %d [%c], n3 = %d [%c]\n",
               n1, line[n1], n2, line[n2], n3, line[n3]);
        if (!isdecint(line[n1]))
            printf("Invalid char for n1\n");
        if (!isdecint(line[n2]))
            printf("Invalid char for n2\n");
        if (line[n3] != '\n')
            printf("Invalid char for n3\n");
        printf("<<%s>>,<<%c>>,%d,%d\n", str, chr, i1, i2);
    }
    return 0;
}

data2

abc,f,123,234☐
abc,  f  , 345    , 456  ☐
abc,f,567,678__SOMERUBBISH☐
xyz,f, 1234, 2345☐
xyz,f,-3456 ,-4567 ☐
xyz,f,+5678,+6789☐
xyz,f,+ 5678,- 6789 X☐

样品运行

$ ./ws2 < data2
Line: <<abc,f,123,234>>
rc = 4: success
n1 = 6 [1], n2 = 10 [2], n3 = 13 [
]
<<abc>>,<<f>>,123,234
Line: <<abc,  f  , 345    , 456  >>
rc = 2: partial failure
n1 = 6 [f], n2 = 10 [ ], n3 = 13 [5]
Invalid char for n1
Invalid char for n2
Invalid char for n3
<<abc>>,<< >>,123,234
Line: <<abc,f,567,678__SOMERUBBISH>>
rc = 4: success
n1 = 6 [5], n2 = 10 [6], n3 = 13 [_]
Invalid char for n3
<<abc>>,<<f>>,567,678
Line: <<xyz,f, 1234, 2345>>
rc = 4: success
n1 = 6 [ ], n2 = 12 [ ], n3 = 17 [
]
Invalid char for n1
Invalid char for n2
<<xyz>>,<<f>>,1234,2345
Line: <<xyz,f,-3456 ,-4567 >>
rc = 3: partial failure
n1 = 6 [-], n2 = 12 [,], n3 = 17 [7]
Invalid char for n2
Invalid char for n3
<<xyz>>,<<f>>,-3456,2345
Line: <<xyz,f,+5678,+6789>>
rc = 4: success
n1 = 6 [+], n2 = 12 [+], n3 = 17 [
]
<<xyz>>,<<f>>,5678,6789
Line: <<xyz,f,+ 5678,- 6789 X>>
rc = 2: partial failure
n1 = 6 [+], n2 = 12 [,], n3 = 17 [8]
Invalid char for n2
Invalid char for n3
<<xyz>>,<<f>>,5678,6789

答案 2 :(得分:0)

来自sscanf的手册页:

  

[

     

匹配指定集合中的非空字符序列   接受的人物;下一个指针必须是指向char的指针,并且   字符串中的所有字符必须有足够的空间,加上a   终止空字节。通常跳过领先的白色空间是   抑制。字符串由a中的字符组成(或不在a中)   特别设定;该集合由open之间的字符定义   括号[字符和近括号]字符。该集不包括在内   如果开括号后的第一个字符是a,则为那些字符   抑扬(^)。要在集合中包含一个小括号,请将其设为   开放括号或回旋后的第一个字符;任何其他   位置将结束该集。连字符 - 也很特别;   当放置在两个其他角色之间时,它会添加所有干预   集合中的字符。要包含连字符,请将其设为最后一个字符   在最后的紧密支架之前。例如,[^] 0-9-]表示集合   “除了近括号,零到九和连字符之外的所有东西”。该   字符串以不在(或者,带有)的字符的外观结束   在设置或当字段宽度用完时,使用抑制旋转。

这可以让您更好地控制输入。 (但它基本上是正则表达式的糟糕版本。)

答案 3 :(得分:0)

您可以先将字段读入字符串,检查字符串是否以空格开头或结尾,然后根据该字符串继续。

这是一个演示检测逻辑的示例程序。

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

int startsOrEndsWithSpace(char str[]) 
{
   return (isspace(str[0]) || isspace(str[strlen(str)-2])); 
}

void testSccanf(char const source[],
                char str1[],
                char str2[],
                char str3[],
                char str4[])
{
   int n = sscanf(source, "%[^,],%[^,],%[^,],%[^,]", str1, str2, str3, str4);
   if ( n != 4 )
   {
      // Problem
      printf("Found only %d fields.\n", n);
   }

   if ( n >= 1 && startsOrEndsWithSpace(str1) )
   {
      printf("1st field is not good\n");
   }

   if ( n >= 2 && startsOrEndsWithSpace(str2) )
   {
      printf("2nd field is not good\n");
   }

   if ( n >=3 && startsOrEndsWithSpace(str3) )
   {
      printf("3rd field is not good\n");
   }

   if ( startsOrEndsWithSpace(str4) )
   {
      printf("4th field is not good\n");
   }
}

int main ()
{
   char str1[50];
   char str2[50];
   char str3[50];
   char str4[50];

   testSccanf("abc,  f  , 123    , 234  ", str1, str2, str3, str4);
   testSccanf("abc,f,123,234", str1, str2, str3, str4);

   return (0);
}

答案 4 :(得分:-1)

使用getchar()查看下一个字符是换行符还是空格。