如何检查%

时间:2018-09-18 22:53:24

标签: c file-io scanf

我有以下代码行从文本文件中读取输入:

a = scanf("(%d)%[^(](%d)(%d)", a1, arr, b1, c1);

此文件的正常输入行为: (4)1234(1)(1234)

除了检查scanf()之外,%d还可以用来标识与发送的模板格式不匹配的特定输入项吗? (在开头缺少括号等)。 4)1234(5)(1234)

根据我的研究,似乎scanf()很难做到这一点,由于scanf()返回4(对于这种情况),这甚至可能是不可能的,但是我想看看是否有可以执行的解决方法。

1 个答案:

答案 0 :(得分:0)

下面的我的解析器。我使用gnu扩展名fmemopen打开内存中的示例输入,并使用getline读取整行(和strerror_r)。
然后,我分析整行,返回错误负值,处理所有strtol可以设置的错误,处理超出范围的错误,错误的标记'('和')',太长的行,等等。

#define _GNU_SOURCE 1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

/**
 * if expression 'expr' is true, then:
 * print error message including custom message on a single line
 * including errno and strerror
 * then return the value "ret".
 */
#define RET_ON_ERR(expr, ret, msg, ...)  do{ \
    if(expr) { \
        const int  _errno_sav = errno; \
        fprintf(stderr, \
            "%s:%d: %s(): error: expresssion '%s' failed: ", \
            __FILE__, __LINE__, __func__, #expr); \
        if (_errno_sav) { \
            char buf[22]; \
            fprintf(stderr, "errno: %d '%s' ", \
                _errno_sav, strerror_r(_errno_sav, buf, sizeof(buf))); \
            errno = 0; \
        } \
        fprintf(stderr, "" msg "\n", ##__VA_ARGS__); \
        return (ret); \
    } \
}while(0)

/**
 * Same as RET_ON_ERR but the value -__LINE__ is returned
 */
#define RET_LINE_ON_ERR(expr, msg, ...)  RET_ON_ERR((expr), -__LINE__, msg, ##__VA_ARGS__)

/**
 * Returns the array size of a statically allocated array
 */
#define ARRAY_SIZE(arr)  (sizeof(arr)/sizeof(arr[0]))

/**
 * Returns -1 and prints error message on conversion failure
 * @param val0 outputted value
 * @param ptr input string
 * @param endptr0 outputted end pointer
 * @param min minimum value to be returned
 * @param max maximum value to be returned
 * On error negative line number is returned and outputted variables are not set.
 */
int strtol_safe(long *val0, const char ptr[], const char *endptr0[], long min, long max)
{
    assert(val0 != NULL);
    assert(ptr != NULL);

    RET_LINE_ON_ERR(!isdigit(ptr[0]), "string does not start with numbers '%s'", ptr);

    // probably writing my own loop would be faster....
    char *endptr = NULL;
    errno = 0;
    const long val = strtol(ptr, &endptr, 10);
    RET_LINE_ON_ERR(errno == ERANGE && (val == LONG_MAX || val == LONG_MIN), "conversion out of range");
    RET_LINE_ON_ERR(errno != 0, "internal error");
    RET_LINE_ON_ERR(endptr == ptr, "no digits were found");

    RET_LINE_ON_ERR(!(min <= val && val <= max), "%ld out of range (%ld,%ld)", val, min, max);

    *val0 = val;
    if (endptr0 != NULL) {
        *endptr0 = endptr;
    }

    return 0;
}

/**
 * Represents out data!
 */
struct data_s {
    int a1;
    char arr[20];
    int b1;
    int c1;
};

/**
 * Fill single data variable parsing 'line'
 * On error prints errorr message and returns negative value of the line where the error was found.
 */
int data_readline(struct data_s *data, const char line[])
{
    assert(data != NULL);
    assert(line != NULL);
    int tmp;
    long ltmp;

    RET_LINE_ON_ERR(line++[0] != '(', "line '%s' first ( not found", line);

    tmp = strtol_safe(&ltmp, line, &line, INT_MIN, INT_MAX);
    RET_ON_ERR(tmp, tmp, "error on reading first number");
    data->a1 = ltmp;

    RET_LINE_ON_ERR(line++[0] != ')', "line '%s' first ) was not fond", line);

    // read the second field consisting of only numbers until '(' is found
    for (char *out = data->arr; line[0] != '('; ++line, ++out) {
        RET_LINE_ON_ERR(!isdigit(line[0]), 
            "the second token must consist of numbers only");
        RET_LINE_ON_ERR(out - data->arr >= ARRAY_SIZE(data->arr),
            "array overflow when reading the second field from line '%s'", line);
        *out = line[0];
    }

    RET_LINE_ON_ERR(line++[0] != '(', "line '%s' second ( not found", line);

    tmp = strtol_safe(&ltmp, line, &line, INT_MIN, INT_MAX);
    RET_ON_ERR(tmp, tmp, "error on reading first number");
    data->b1 = ltmp;

    RET_LINE_ON_ERR(line++[0] != ')', "line '%s' second ) not found", line);

    RET_LINE_ON_ERR(line++[0] != '(', "line '%s' third ( not found", line);

    tmp = strtol_safe(&ltmp, line, &line, INT_MIN, INT_MAX);
    RET_ON_ERR(tmp, tmp, "error on reading first number");
    data->c1 = ltmp;

    RET_LINE_ON_ERR(line++[0] != ')', "line '%s' third ) not found", line);

    RET_LINE_ON_ERR(line[0] != '\n' && line[0] != '\0', 
        "line '%s' does not end after reading tokens", line);

    return 0;
}


int main()
{
    // create FILE object reading from custom buffer, just for testing
    char buf[] = "(4)1234(1)(1234)";
    FILE * const f = fmemopen(buf, ARRAY_SIZE(buf), "r");
    RET_LINE_ON_ERR(f == NULL, "error fmemopen");

    // read one line of input.
    char *line = NULL;
    size_t n = 0;
    RET_LINE_ON_ERR(getline(&line, &n, f) < 0, "getline error");

    // read data structure
    struct data_s data = {0};
    int ret;
    ret = data_readline(&data, line);
    if (ret < 0) {
        fprintf(stderr, "error from data_readline: %d\n", ret);
        exit(-1);
    }

    // print output
    printf("readed first line consist of:\n"
            "a1=%d arr='%s' b1=%d c1=%d\n"
            , 
            data.a1, data.arr, data.b1, data.c1
    );

    close(f);
    return 0;
}

实时版本可在onlinegdb上使用。