sscanf - 使用 optional/empty 格式说明符解析帧
sscanf - Parse frame with optional/empty format specifiers
我正在尝试解析按以下方案格式化的帧:
$[number],[number],[number],<string>;[string]~<string>
用'[]'包围的参数是可选的,用'<>'包围的参数总是存在定义:
因此,以下帧都是正确的:
[=12=],0,0,thisIsFirstString;secondString~thirdOne
[=12=],,0,firstString;~thirdOne
$,,,firstString;~thirdString
目前,当所有元素都存在时,我可以使用以下代码解析框架
int main() {
char frame[100] = ",2,3,string1;string2~string3";
char num1[10], num2[10], num3[10], str1[100], str2[100], str3[100];
printf("frame : %s\n", frame);
sscanf(frame,"$%[^,],%[^,],%[^,],%[^;];%[^~]~%s", num1, num2, num3, str1, str2, str3);
printf("Number 1 : %s\n", num1);
printf("Number 2 : %s\n", num2);
printf("Number 3 : %s\n", num3);
printf("String 1 : %s\n", str1);
printf("String 2 : %s\n", str2);
printf("String 3 : %s\n", str3);
return 0;
}
结果如下
frame : ,2,3,string1;string2~string3
Number 1 : 1
Number 2 : 2
Number 3 : 3
String 1 : string1
String 2 : string2
String 3 : string3
但是,如果缺少一个参数,前面的参数可以很好地解析,但缺少参数后面的参数就不行。
frame : ,,3,string1;string2~string3
Number 1 : 1
Number 2 :
Number 3 :
String 1 :��/�
String 2 : �\<��
String 3 : $[<��
frame : ,2,3,string1;~string3
Number 1 : 1
Number 2 : 2
Number 3 : 3
String 1 : string1
String 2 : h�v��
String 3 : ��v��
我如何指定 sscanf
帧中可能缺少某些参数,因此在这种情况下它们将被丢弃?
猜测最好还是自己写解析器函数:
#define _GNU_SOURCE 1
#define _POSIX_C_SOURCE 1
#include <stdio.h>
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#define __arraycount(x) (sizeof(x)/sizeof(x[0]))
// from
static char *mystrtok(char **m,char *s,char c)
{
char *p = s ? s : *m;
if (!*p)
return NULL;
*m = strchr(p, c);
if (*m)
*(*m)++ = '[=10=]';
else
*m = p + strlen(p);
return p;
}
static char getseparator(size_t i)
{
return i <= 2 ? ',' : i == 3 ? ';' : i == 4 ? '~' : 0;
}
int main()
{
char ***output = NULL;
size_t outputlen = 0;
const size_t outputstrings = 6;
char *line = NULL;
size_t linelen = 0;
size_t linecnt;
for (linecnt = 0; getline(&line, &linelen, stdin) > 0; ++linecnt) {
if (line[0] != '$') {
printf("Lines not starting with $ are ignored\n");
continue;
}
// alloc memory for new set of 6 strings
output = realloc(output, sizeof(*output) * outputlen++);
if (output == NULL) {
fprintf(stderr, "%d Error allocating memory", __LINE__);
return -1;
}
output[outputlen - 1] = malloc(sizeof(*output[outputlen - 1]) * outputstrings);
if (output[outputlen - 1] == NULL) {
fprintf(stderr, "%d Error allocating memory", __LINE__);
return -1;
}
// remove closing newline
line[strlen(line)-1] = '[=10=]';
//printf("Read line `%s`\n", line);
char *token;
char *rest = &line[1];
char *state;
size_t i;
for (i = 0, token = mystrtok(&state, &line[1], getseparator(i));
i < outputstrings && token != NULL;
++i, token = mystrtok(&state, NULL, getseparator(i))) {
output[outputlen - 1][i] = strdup(token);
if (output[outputlen - 1][i] == NULL) {
fprintf(stderr, "%d Error allocating memory", __LINE__);
return -1;
}
//printf("Read %d string: `%s`\n", i, output[outputlen - 1][i]);
}
if (i != outputstrings) {
printf("Malformed line: %s %d %p \n", line, i, token);
continue;
}
}
free(line);
for (size_t i = 0; i < outputlen; ++i) {
for (size_t j = 0; j < outputstrings; ++j) {
printf("From line %d the string num %d: `%s`\n", i, j, output[i][j]);
}
}
for (size_t i = 0; i < outputlen; ++i) {
for (size_t j = 0; j < outputstrings; ++j) {
free(output[i][j]);
}
free(output[i]);
}
free(output);
return 0;
}
哪个输入:
[=11=],0,0,thisIsFirstString;secondString~thirdOne
[=11=],,0,firstString;~thirdOne
$,,,firstString;~thirdString
产生结果:
From line 0 the string num 0: `0`
From line 0 the string num 1: `0`
From line 0 the string num 2: `0`
From line 0 the string num 3: `thisIsFirstString`
From line 0 the string num 4: `secondString`
From line 0 the string num 5: `thirdOne`
From line 1 the string num 0: `0`
From line 1 the string num 1: ``
From line 1 the string num 2: `0`
From line 1 the string num 3: `firstString`
From line 1 the string num 4: ``
From line 1 the string num 5: `thirdOne`
From line 2 the string num 0: ``
From line 2 the string num 1: ``
From line 2 the string num 2: ``
From line 2 the string num 3: `firstString`
From line 2 the string num 4: ``
From line 2 the string num 5: `thirdStrin`
正如其他人所说 scanf()
系列函数可能不适合此操作,因为在输入字符串不是预期格式的情况下无法进行充分的错误处理。
但是如果您确定输入字符串将始终采用该格式,则可以使用指向输入字符串相关部分的指针,然后使用 sscanf()
.[=29= 对其进行处理]
首先将所有字符数组初始化为空字符串,这样打印时就不会显示乱码了。喜欢
char num1[10]="";
对于提取的参数要写入的所有 char
个数组。
声明一个字符指针,使其指向输入字符串frame
的开头。
char *ptr=frame;
现在检查第一个参数,它是可选的,如
if(sscanf(ptr, "$%[^,],", num1)==1)
{
//parameter 1 is present.
ptr+=strlen(num1);
}
ptr+=2;
如果参数存在,我们将 ptr
增加参数字符串的长度,并为 '$' 和逗号进一步增加 2
。
接下来的两个参数也是可选的。
if(sscanf(ptr, "%[^,]", num2)==1)
{
//Parameter 2 is present
ptr+=strlen(num2);
}
ptr+=1;
if(sscanf(ptr, "%[^,]", num3)==1)
{
//Parameter 3 is present
ptr+=strlen(num3);
}
ptr+=1;
下一个参数,参数 4,不是可选的。
sscanf(ptr, "%[^;]", str1);
ptr+=strlen(str1)+1;
现在为可选参数5,
if(sscanf(ptr, "%[^~]", str2)==1)
{
//Parameter 5 is present
ptr+=strlen(str2);
}
ptr+=1; //for ~
最后是非可选参数6,
sscanf(ptr, "%s", str3);
为简洁起见,省略了可能的错误检查。为了防止溢出,请在 scanf()
格式字符串中使用宽度说明符,如
sscanf(ptr, "%9[^,],", num2);
其中 9
比 num2
字符数组的长度小一。
并且在您的程序中,如果 %[^,]
部分对应于空字符串,sscanf()
将有效地停止分配给变量。
scanf()
不能转换handle空字符类,而strtok()
将每一个分隔符序列视为一个单独的分隔符,这确实只适用于白色space。
这里有一个简单的类似 scanf 的非贪婪解析器供您使用:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int my_sscanf(const char *s, const char *fmt, ...) {
int res = 0;
va_list ap;
va_start(ap, fmt);
for (; *fmt; fmt++) {
if (*fmt == '%') {
fmt++;
if (*fmt == 's') {
size_t i = 0, size = va_arg(ap, size_t);
char *dest = va_arg(ap, char *);
while (*s && *s != fmt[1]) {
if (i + 1 < size)
dest[i++] = *s;
s++;
}
if (size)
dest[i] = '[=10=]';
res++;
continue;
}
if (*fmt == 'd') {
*va_arg(ap, int *) = strtol(s, (char **)&s, 10);
res++;
continue;
}
if (*fmt == 'i') {
*va_arg(ap, int *) = strtol(s, (char **)&s, 0);
res++;
continue;
}
/* add support for other conversions as you wish */
if (*fmt != '%')
return -1;
}
if (*fmt == ' ') {
while (isspace((unsigned char)*s))
s++;
continue;
}
if (*s == *fmt) {
s++;
} else {
break;
}
}
va_end(ap);
return res;
}
int main() {
char frame[100] = ",,3,string1;~string3";
char str1[100], str2[100], str3[100];
int res, num1, num2, num3;
printf("frame : %s\n", frame);
res = my_sscanf(frame, "$%d,%d,%d,%s;%s~%s", &num1, &num2, &num3,
sizeof str1, str1, sizeof str2, str2, sizeof str3, str3);
if (res == 6) {
printf("Number 1 : %d\n", num1);
printf("Number 2 : %d\n", num2);
printf("Number 3 : %d\n", num3);
printf("String 1 : %s\n", str1);
printf("String 2 : %s\n", str2);
printf("String 3 : %s\n", str3);
} else {
printf("my_scanf returned %d\n", res);
}
return 0;
}
我正在尝试解析按以下方案格式化的帧:
$[number],[number],[number],<string>;[string]~<string>
用'[]'包围的参数是可选的,用'<>'包围的参数总是存在定义:
因此,以下帧都是正确的:
[=12=],0,0,thisIsFirstString;secondString~thirdOne
[=12=],,0,firstString;~thirdOne
$,,,firstString;~thirdString
目前,当所有元素都存在时,我可以使用以下代码解析框架
int main() {
char frame[100] = ",2,3,string1;string2~string3";
char num1[10], num2[10], num3[10], str1[100], str2[100], str3[100];
printf("frame : %s\n", frame);
sscanf(frame,"$%[^,],%[^,],%[^,],%[^;];%[^~]~%s", num1, num2, num3, str1, str2, str3);
printf("Number 1 : %s\n", num1);
printf("Number 2 : %s\n", num2);
printf("Number 3 : %s\n", num3);
printf("String 1 : %s\n", str1);
printf("String 2 : %s\n", str2);
printf("String 3 : %s\n", str3);
return 0;
}
结果如下
frame : ,2,3,string1;string2~string3
Number 1 : 1
Number 2 : 2
Number 3 : 3
String 1 : string1
String 2 : string2
String 3 : string3
但是,如果缺少一个参数,前面的参数可以很好地解析,但缺少参数后面的参数就不行。
frame : ,,3,string1;string2~string3
Number 1 : 1
Number 2 :
Number 3 :
String 1 :��/�
String 2 : �\<��
String 3 : $[<��
frame : ,2,3,string1;~string3
Number 1 : 1
Number 2 : 2
Number 3 : 3
String 1 : string1
String 2 : h�v��
String 3 : ��v��
我如何指定 sscanf
帧中可能缺少某些参数,因此在这种情况下它们将被丢弃?
猜测最好还是自己写解析器函数:
#define _GNU_SOURCE 1
#define _POSIX_C_SOURCE 1
#include <stdio.h>
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#define __arraycount(x) (sizeof(x)/sizeof(x[0]))
// from
static char *mystrtok(char **m,char *s,char c)
{
char *p = s ? s : *m;
if (!*p)
return NULL;
*m = strchr(p, c);
if (*m)
*(*m)++ = '[=10=]';
else
*m = p + strlen(p);
return p;
}
static char getseparator(size_t i)
{
return i <= 2 ? ',' : i == 3 ? ';' : i == 4 ? '~' : 0;
}
int main()
{
char ***output = NULL;
size_t outputlen = 0;
const size_t outputstrings = 6;
char *line = NULL;
size_t linelen = 0;
size_t linecnt;
for (linecnt = 0; getline(&line, &linelen, stdin) > 0; ++linecnt) {
if (line[0] != '$') {
printf("Lines not starting with $ are ignored\n");
continue;
}
// alloc memory for new set of 6 strings
output = realloc(output, sizeof(*output) * outputlen++);
if (output == NULL) {
fprintf(stderr, "%d Error allocating memory", __LINE__);
return -1;
}
output[outputlen - 1] = malloc(sizeof(*output[outputlen - 1]) * outputstrings);
if (output[outputlen - 1] == NULL) {
fprintf(stderr, "%d Error allocating memory", __LINE__);
return -1;
}
// remove closing newline
line[strlen(line)-1] = '[=10=]';
//printf("Read line `%s`\n", line);
char *token;
char *rest = &line[1];
char *state;
size_t i;
for (i = 0, token = mystrtok(&state, &line[1], getseparator(i));
i < outputstrings && token != NULL;
++i, token = mystrtok(&state, NULL, getseparator(i))) {
output[outputlen - 1][i] = strdup(token);
if (output[outputlen - 1][i] == NULL) {
fprintf(stderr, "%d Error allocating memory", __LINE__);
return -1;
}
//printf("Read %d string: `%s`\n", i, output[outputlen - 1][i]);
}
if (i != outputstrings) {
printf("Malformed line: %s %d %p \n", line, i, token);
continue;
}
}
free(line);
for (size_t i = 0; i < outputlen; ++i) {
for (size_t j = 0; j < outputstrings; ++j) {
printf("From line %d the string num %d: `%s`\n", i, j, output[i][j]);
}
}
for (size_t i = 0; i < outputlen; ++i) {
for (size_t j = 0; j < outputstrings; ++j) {
free(output[i][j]);
}
free(output[i]);
}
free(output);
return 0;
}
哪个输入:
[=11=],0,0,thisIsFirstString;secondString~thirdOne
[=11=],,0,firstString;~thirdOne
$,,,firstString;~thirdString
产生结果:
From line 0 the string num 0: `0`
From line 0 the string num 1: `0`
From line 0 the string num 2: `0`
From line 0 the string num 3: `thisIsFirstString`
From line 0 the string num 4: `secondString`
From line 0 the string num 5: `thirdOne`
From line 1 the string num 0: `0`
From line 1 the string num 1: ``
From line 1 the string num 2: `0`
From line 1 the string num 3: `firstString`
From line 1 the string num 4: ``
From line 1 the string num 5: `thirdOne`
From line 2 the string num 0: ``
From line 2 the string num 1: ``
From line 2 the string num 2: ``
From line 2 the string num 3: `firstString`
From line 2 the string num 4: ``
From line 2 the string num 5: `thirdStrin`
正如其他人所说 scanf()
系列函数可能不适合此操作,因为在输入字符串不是预期格式的情况下无法进行充分的错误处理。
但是如果您确定输入字符串将始终采用该格式,则可以使用指向输入字符串相关部分的指针,然后使用 sscanf()
.[=29= 对其进行处理]
首先将所有字符数组初始化为空字符串,这样打印时就不会显示乱码了。喜欢
char num1[10]="";
对于提取的参数要写入的所有 char
个数组。
声明一个字符指针,使其指向输入字符串frame
的开头。
char *ptr=frame;
现在检查第一个参数,它是可选的,如
if(sscanf(ptr, "$%[^,],", num1)==1)
{
//parameter 1 is present.
ptr+=strlen(num1);
}
ptr+=2;
如果参数存在,我们将 ptr
增加参数字符串的长度,并为 '$' 和逗号进一步增加 2
。
接下来的两个参数也是可选的。
if(sscanf(ptr, "%[^,]", num2)==1)
{
//Parameter 2 is present
ptr+=strlen(num2);
}
ptr+=1;
if(sscanf(ptr, "%[^,]", num3)==1)
{
//Parameter 3 is present
ptr+=strlen(num3);
}
ptr+=1;
下一个参数,参数 4,不是可选的。
sscanf(ptr, "%[^;]", str1);
ptr+=strlen(str1)+1;
现在为可选参数5,
if(sscanf(ptr, "%[^~]", str2)==1)
{
//Parameter 5 is present
ptr+=strlen(str2);
}
ptr+=1; //for ~
最后是非可选参数6,
sscanf(ptr, "%s", str3);
为简洁起见,省略了可能的错误检查。为了防止溢出,请在 scanf()
格式字符串中使用宽度说明符,如
sscanf(ptr, "%9[^,],", num2);
其中 9
比 num2
字符数组的长度小一。
并且在您的程序中,如果 %[^,]
部分对应于空字符串,sscanf()
将有效地停止分配给变量。
scanf()
不能转换handle空字符类,而strtok()
将每一个分隔符序列视为一个单独的分隔符,这确实只适用于白色space。
这里有一个简单的类似 scanf 的非贪婪解析器供您使用:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int my_sscanf(const char *s, const char *fmt, ...) {
int res = 0;
va_list ap;
va_start(ap, fmt);
for (; *fmt; fmt++) {
if (*fmt == '%') {
fmt++;
if (*fmt == 's') {
size_t i = 0, size = va_arg(ap, size_t);
char *dest = va_arg(ap, char *);
while (*s && *s != fmt[1]) {
if (i + 1 < size)
dest[i++] = *s;
s++;
}
if (size)
dest[i] = '[=10=]';
res++;
continue;
}
if (*fmt == 'd') {
*va_arg(ap, int *) = strtol(s, (char **)&s, 10);
res++;
continue;
}
if (*fmt == 'i') {
*va_arg(ap, int *) = strtol(s, (char **)&s, 0);
res++;
continue;
}
/* add support for other conversions as you wish */
if (*fmt != '%')
return -1;
}
if (*fmt == ' ') {
while (isspace((unsigned char)*s))
s++;
continue;
}
if (*s == *fmt) {
s++;
} else {
break;
}
}
va_end(ap);
return res;
}
int main() {
char frame[100] = ",,3,string1;~string3";
char str1[100], str2[100], str3[100];
int res, num1, num2, num3;
printf("frame : %s\n", frame);
res = my_sscanf(frame, "$%d,%d,%d,%s;%s~%s", &num1, &num2, &num3,
sizeof str1, str1, sizeof str2, str2, sizeof str3, str3);
if (res == 6) {
printf("Number 1 : %d\n", num1);
printf("Number 2 : %d\n", num2);
printf("Number 3 : %d\n", num3);
printf("String 1 : %s\n", str1);
printf("String 2 : %s\n", str2);
printf("String 3 : %s\n", str3);
} else {
printf("my_scanf returned %d\n", res);
}
return 0;
}