如何在支持空字段的同时使用sscanf解析逗号分隔字符串中的字段?

我有一个逗号分隔的字符串,可能包含空字段。 例如:

1,2,,4 

使用基本的

 sscanf(string,"%[^,],%[^,],%[^,],%[^,],%[^,]", &val1, &val2, &val3, &val4); 

我得到空字段之前的所有值,以及从空字段开始的意外结果。

当我从sscanf()中删除空字段的表达式时,

 sscanf(string,"%[^,],%[^,],,%[^,],%[^,]", &val1, &val2, &val3, &val4); 

一切都很好。

由于我不知道何时会得到一个空字段,有没有办法重写表达式以优雅地处理空字段?

如果使用strtok和逗号作为分隔符,您将获得一个字符串列表,其中一个或多个字符串将为null / zero length。

有关更多信息,请查看我的答案 。

男人sscanf :

[匹配指定的一组接受字符中的非空字符序列;

(重点补充)。

这看起来就像您正在处理CSV值。 如果你需要扩展它以处理带引号的字符串(例如,字段可以包含逗号),你会发现scanf -family无法处理格式的所有复杂性。 因此,您需要使用专门设计的代码来处理(您的变体)CSV格式。

您将在C语言和C ++中的“ 编程实践 ”中找到关于设置CSV库实现的讨论。 毫无疑问,还有很多其他可用的。

scanf()返回分配的项目数。 也许你可以使用那些信息……

 char *data = "1, 2,,, 5, 6"; int a[6]; int assigned = sscanf(data, "%d,%d,%d,%d,%d,%d", a, a+1, a+2, a+3, a+4, a+5); if (assigned < 6) { char fmt[18]; switch (assigned) { default: assert(0 && "this did not happen"); break; case 0: fmt = ",%d,%d,%d,%d,%d"; break; case 1: fmt = "%d,,%d,%d,%d,%d"; break; case 2: fmt = "%d,%d,,%d,%d,%d"; break; case 3: fmt = "%d,%d,%d,,%d,%d"; break; case 4: fmt = "%d,%d,%d,%d,,%d"; break; case 5: fmt = "%d,%d,%d,%d,%d,"; break; } sscanf(data, fmt, a+(assigned<=0), a+1+(assigned<=1), a+2+(assigned<=2), a+3+(assigned<=3), a+4+(assigned<=4)); } 

啊! 这只是1个缺失值
正如其他答案所指出的那样,以“通常”的方式解析字符串要好得多: fgets()strtok()

这是我的扫描逗号分隔的int值的版本。 该代码检测空字段和非整数字段。

 #include  #include  int main(){ char str[] = " 1 , 2 x, , 4 "; printf("str: '%s'\n", str ); for( char *s2 = str; s2; ){ while( *s2 == ' ' || *s2 == '\t' ) s2++; char *s1 = strsep( &s2, "," ); if( !*s1 ){ printf("val: (empty)\n" ); } else{ int val; char ch; int ret = sscanf( s1, " %i %c", &val, &ch ); if( ret != 1 ){ printf("val: (syntax error)\n" ); } else{ printf("val: %i\n", val ); } } } return 0; } 

结果:

 str: ' 1 , 2 x, , 4 ' val: 1 val: (syntax error) val: (empty) val: 4 

在’%’后放一个’*’以跳过阅读。 另外,例如,可以只读取3个字符,注意’%3s’。

我来到这里寻找同一个问题的答案。 我也不想留下scanffunction。 最后,我自己构建了一个zsscanf,在那里我解析了格式,逐个扫描每个数据并检查sscanf的返回,看看我是否有空读。 这有点我的特殊情况:我只想要一些字段,其中一些可能是空的,并且不能假设分隔符。

 #include  #include  int zsscanf(char *data, char *format, ...) { va_list argp; va_start(argp, format); int fptr = 0, sptr = 0, iptr = 0, isptr = 0, ok, saved = 0; char def[32]; while (1) { if (format[fptr] != '%') { ok = sscanf(&format[fptr], "%28[^%]%n", def, &iptr); if (!ok) break; fptr += iptr; def[iptr] = '%'; def[iptr+1] = 'n'; def[iptr+2] = 0; ok = sscanf(&data[sptr], def, &isptr); if (!ok) break; sptr += isptr; } else if (format[fptr+1] == '%') { if (data[sptr] == '%') { fptr += 2; sptr += 1; } else { ok = -1; break; } } else { void *savehere = NULL; ok = sscanf(&format[fptr], "%%%28[^%]%n", &def[1], &iptr); if (!ok) break; fptr += iptr; def[0] = '%'; def[iptr] = '%'; def[iptr+1] = 'n'; def[iptr+2] = 0; isptr = 0; if (def[1] != '*') { savehere = va_arg(argp, void*); ok = sscanf(&data[sptr], def, savehere, &isptr); if (ok == 0 && isptr == 0) { // Let's assume only char types. Won't hurt in other cases. ((char*)savehere)[0] = 0; ok = 1; } if (ok > 0) { saved++; } } else { ok = sscanf(&data[sptr], def, &isptr) == 0; } if (ok < 0) break; sptr += isptr; } } va_end(argp); return saved == 0 ? ok : saved; } int main() { char *format = "%15[^\t;,]%*1[\t;,]" // NameId "%*[^\t;,]%*1[\t;,]" // Name "%*[^\t;,]%*1[\t;,]" // Abbreviation "%*[^\t;,]%*1[\t;,]" // Description "%31[^\t;,]"; // Electrical Line char nameId[16]; char elect[32]; char *line1 = "TVC-CCTV-0002\tTVC-CCTV-0002\tTVC-CCTV-0002\tCCTV DOMO CAMERA 21-32-29\tELECTRICAL_TopoLine_823\tfoo\tbar"; char *line2 = "TVC-CCTV-0000;;;;;foo;bar;"; int ok = zsscanf(line1, format, nameId, elect); printf ("%d: |%s|%s|\n", ok, nameId, elect); ok = zsscanf(line2, format, nameId, elect); printf ("%d: |%s|%s|\n", ok, nameId, elect); return 0; } 

输出:

  2: |TVC-CCTV-0002|ELECTRICAL_TopoLine_823| 2: |TVC-CCTV-0000|| 

请注意,它没有经过全面测试并且有严重的限制(最明显的限制:仅接受%...s%...c%...[...]并要求分隔符为%...[...] ;否则我真的很想关心格式字符串,这种方式我只关心% )。

我必须修改这段代码才能正常工作:

 //rm token_pure;gcc -Wall -O3 -o token_pure token_pure.c; ./token_pure #include  #include  int main () { char str[] = " 1 , 2 x, , 4 "; char *s1; char *s2; s2=(void*)&str; //this is here to avoid warning of assignment from incompatible pointer type do { while( *s2 == ' ' || *s2 == '\t' ) s2++; s1 = strsep( &s2, "," ); if( !*s1 ){ printf("val: (empty)\n" ); } else{ int val; char ch; int ret = sscanf( s1, " %i %c", &val, &ch ); if( ret != 1 ){ printf("val: (syntax error)\n" ); } else{ printf("val: %i\n", val ); } } } while (s2!=0 ); return 0; } 

和输出:

 val: 1 val: (syntax error) val: (empty) val: 4 

我对制表符分隔的TSV文件进行了修改,希望它可能有所帮助:

 //rm token_tab;gcc -Wall -O3 -o token_tab token_tab.c; ./token_tab #include  #include  int main () { // char str[] = " 1 2 x text 4 "; char str[] = " 1\t 2 x\t\t text\t4 "; char *s1; char *s2; s2=(void*)&str; //this is here to avoid warning of assignment from incompatible pointer type do { while( *s2 == ' ') s2++; s1 = strsep( &s2, "\t" ); if( !*s1 ){ printf("val: (empty)\n" ); } else{ int val; char ch; int ret = sscanf( s1, " %i %c", &val, &ch ); if( ret != 1 ){ printf("val: (syntax error or string)=%s\n", s1 ); } else{ printf("val: %i\n", val ); } } } while (s2!=0 ); return 0; } 

和输出:

 val: 1 val: (syntax error or string)=2 x val: (empty) val: (syntax error or string)=text val: 4 

这里列出了strtok()的一些问题 : http : //benpfaff.org/writings/clc/strtok.html

因此,最好避免使用strtok

现在,考虑一个包含空字段的字符串,如下所示:

 char myCSVString[101] = "-1.4,2.6,,-0.24,1.26"; // specify input here 

您可以使用简单的函数来转换CSV格式的String以将它们读取到float数组

 int strCSV2Float(float *strFloatArray , char *myCSVStringing); 

请在下面找到用法

 #include  #include  int strCSV2Float(float *strFloatArray , char *myCSVStringing); void main() { char myCSVString[101] = "-1.4,2.6,,-0.24,1.26"; // specify input here float floatArr[10]; // specify size here int totalValues = 0; printf("myCSVString == %s \n",&myCSVString[0]); totalValues = strCSV2Float(&floatArr[0] , &myCSVString[0]); // call the function here int floatValueCount = 0; for (floatValueCount = 0 ; floatValueCount < totalValues ; floatValueCount++) { printf("floatArr[%d] = %f\n",floatValueCount , floatArr[floatValueCount]); } } int strCSV2Float(float *strFloatArray , char *myCSVStringing) { int strLen = 0; int commaCount =0; // count the number of commas int commaCountOld =0; // count the number of commas int wordEndChar = 0; int wordStartChar = -1; int wordLength =0; for(strLen=0; myCSVStringing[strLen] != '\0'; strLen++) // first get the string length { if ( (myCSVStringing[strLen] == ',') || ( myCSVStringing[strLen+1] == '\0' )) { commaCount++; wordEndChar = strLen; } if ( (commaCount - commaCountOld) > 0 ) { int aIter =0; wordLength = (wordEndChar - wordStartChar); char word[55] = ""; for (aIter = 0; aIter < wordLength; aIter++) { word[aIter] = myCSVStringing[strLen-wordLength+aIter+1]; } if (word[aIter-1] == ',') word[aIter-1] = '\0'; // printf("\n"); word[wordLength] = '\0'; strFloatArray[commaCount-1] = atof(&word[0]); wordLength = 0; wordStartChar = wordEndChar; commaCountOld = commaCount; } } return commaCount; } 

输出如下:

 myCSVString == -1.4,2.6,,-0.24,1.26 floatArr[0] = -1.400000 floatArr[1] = 2.600000 floatArr[2] = 0.000000 floatArr[3] = -0.240000 floatArr[4] = 1.260000