【问题标题】:Detecting mismatches against constants in scanf format string检测与 scanf 格式字符串中的常量不匹配
【发布时间】:2016-09-27 08:14:52
【问题描述】:

来自scanfman页面:

指令是以下之一:

  • 一系列空白字符(空格、制表符、换行符等;参见 isspace(3))。该指令匹配任何数量的 输入中的空格,包括无空格。

  • 一个普通字符(即除空格或 '%' 以外的字符)。该字符必须与下一个字符完全匹配 输入。(强调我的)

  • 以“%”(百分比)字符开头的转换规范。输入的字符序列是 根据这个规范转换,结果放在 对应的指针 争论。如果下一项输入与转换规范不匹配,则转换失败——这是一个匹配 失败。

现在,考虑以下代码:

#include <stdio.h>

int main(void)
{
    const char* fmt = "A %49s B";
    char buf[50];

    printf("%d\n", sscanf("A foo B", fmt, buf));            // 1
    printf("%d\n", sscanf("blah blaaah blah", fmt, buf));   // 0
    printf("%d\n", sscanf("A blah blah", fmt, buf));        // 1

    return 0;
}

第 1 行和第 3 行打印 1,因为匹配 "A" 和 "A" 成功,匹配 "foo"/"blah" 和 %s 也是如此。第 2 行打印 0,因为 "A" 无法与 "blah" 匹配,因此解析停止。

这一切都很好且合乎逻辑,但是我有什么方法可以检测到在所有转换规范都已成功匹配和分配后发生匹配失败?在这种情况下,scanf 返回的值将是我的格式字符串中的转换说明符的数量,所以直到最后我都无法判断匹配是否成功。

换句话说:第 3 行中提供给sscanf 的字符串不是“有效”的,因为它不是A [something] B 格式。我可以使用scanf 来检测这一点,还是strtok 是我唯一的选择?

【问题讨论】:

  • 使用sscanf 对精确的字符串匹配并不可靠,使用正则表达式库来完成手头的任务会更强大。例如,pcre 库 或 GNU 的 regex 库。
  • 你的问题是什么?完成后sscanf 返回。你还期待什么?
  • @t0mm13b: 或者一个可能带有词法分析器的简单适当的解析器。没有足够的信息提供适当的建议。
  • @Olaf 解析器/词法分析器组合将用于更奇特的 DSL,在 OP 的用例中相当简化,使用解析器/词法分析的组合将是矫枉过正。
  • Regexp 也可能是。看我的回答。但我强烈认为 OP 不会解析“blah blaaah blah”所以我的声明是:NEI。

标签: c io scanf


【解决方案1】:

如果您想解析更复杂的输入,请使用适当的解析器/lexxer。否则,看看%nconversion specifier

不消耗任何输入。相应的参数应该是一个指向有符号整数的指针,该整数将通过调用 fscanf 函数从输入流中读取的字符数写入该整数。执行 %n 指令不会增加 fscanf 函数执行完成时返回的赋值计数。没有参数被转换,但有一个被消耗。如果转换规范包含赋值抑制字符或字段宽度,则行为未定义。

您可以多次使用:在最后一次变量转换之后和最后一次。

【讨论】:

    【解决方案2】:

    在格式末尾使用" %n"

    指令:
    " " 扫描 0 个或更多空白。它不会失败。
    "%n" 保存到目前为止已解析的字符数的计数。它不会失败。

    n 设置为 0 并测试它是否发生了变化。只有在前面的整个格式成功时,才会发生更改。还要测试扫描是否以空字符结束 - 从而检测出不需要的文本。

    添加的" " 虽然是可选的,但如果非常有用,因为通常是尾随空格(通常是'\n'),但不会令人反感。它不需要对扫描的文本行进行预处理以删除其行尾。

    #include <stdio.h>
    
    void test(const char *s) {
      const char* fmt = "A %49s B %n";
      char buf[50];
      int n = 0;
      int cnt = sscanf(s, fmt, buf, &n);
      int success = n > 0 && s[n] == '\0';
      printf("sscanf():%2d  n:%2d  success:%d  '%s'\n", cnt, n, success, s);
    }
    
    int main(void) {
      test("A foo B");
      test("blah blaaah blah");
      test("A blah blah");
      test("A foo B ");
      test("A foo B x");
      test("");
      return 0;
    }
    

    输出

    sscanf(): 1  n: 7  success:1  'A foo B'
    sscanf(): 0  n: 0  success:0  'blah blaaah blah'
    sscanf(): 1  n: 0  success:0  'A blah blah'
    sscanf(): 1  n: 8  success:1  'A foo B '
    sscanf(): 1  n: 8  success:0  'A foo B x'
    sscanf():-1  n: 0  success:0  ''
    

    请注意,成功仅由n 决定。如果不成功,则不应使用目标扫描变量,如buf。如果需要部分结果,则使用sscanf()的返回值。

    【讨论】:

      【解决方案3】:

      对于 OP 的用例,regex 同样可以用于匹配模式。

      /* see http://linux.die.net/man/3/regex */
      #include <stdio.h>
      #include <stdlib.h>
      #include <regex.h>
      
      int regexp(const char *);
      
      int main(int argc, char **argv){
          printf("%d\n", regexp("A foo B"));
          printf("%d\n", regexp("blah blaaah blah"));
          printf("%d\n", regexp("A blah blah"));
          return EXIT_SUCCESS;
      }
      int regexp(const char *input_str){
          char buf[100];
          regex_t regex;
          int rcval;
      
          /* compile regexp - see http://linux.die.net/man/3/regcomp */
          rcval = regcomp(&regex, "^A\\s.*\\sB$", 0);
          if (rcval) {
              fprintf(stderr, "Could not compile regex\n");
              return -1;
          }
          /* execute regexp - see http://linux.die.net/man/3/regexec */
          rcval = regexec(&regex, input_str, 0, NULL, 0);
          if (!rcval) {
              fprintf(stdout, "Match\n");
              regfree(&regex);
              return 1;
          }else{
              if (rcval == REG_NOMATCH) {
                  fprintf(stdout, "No match\n");
                  regfree(&regex);
                  return 0;
              }else{
                  regerror(rcval, &regex, msgbuf, sizeof(msgbuf));
                  fprintf(stderr, "Regex match failed: %s\n", msgbuf);
                  regfree(&regex);
                  return -1; 
              }
          }
          return 0; // default to no match
      }
      

      【讨论】:

        【解决方案4】:

        如果你只关心整个格式是否被扫描,而不关心流中的尾随文本,你可以使用这个简洁的方法:

        int success = 0;
        fscanf(stream, "A %49s B%n", buf, &success);
        if(!success) handleError();
        

        诀窍在于,除非整个格式匹配成功,否则不会发生"%n" 转换。由于格式的任何匹配都会消耗流中的一个或多个字符,"%n" 写入的值将始终为非零。因此,当且仅当整个格式匹配时,success 才会被设置为真值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-03-10
          • 2015-01-11
          • 2010-11-26
          • 2012-10-09
          相关资源
          最近更新 更多