13

空のフィールドを含む可能性のあるカンマ区切りの文字列があります。例えば:

1,2,,4

基本的な使い方

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

空のフィールドの前にすべての値を取得し、空のフィールド以降から予期しない結果を取得します。

sscanf() から空のフィールドの式を削除すると、

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

すべてがうまくいきます。

いつ空のフィールドを取得するかわからないので、空のフィールドをエレガントに処理するように式を書き直す方法はありますか?

4

11 に答える 11

12

コンマを区切り文字として使用するstrtokと、1 つ以上の長さが null/ゼロになる文字列のリストが得られます。

詳細については、こちらの回答をご覧ください。

于 2009-10-02T10:24:51.433 に答える
0

コンマ区切りのint値をスキャンする私のバージョンは次のとおりです。このコードは、空のフィールドと整数以外のフィールドを検出します。

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

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
于 2009-10-02T14:10:33.680 に答える
0

タブ区切りの TSV ファイルに変更を加えました。

//rm token_tab;gcc -Wall -O3 -o token_tab token_tab.c; ./token_tab 
#include <stdio.h>
#include <string.h>

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
于 2014-04-29T21:01:13.750 に答える
0

これは、現在 CSV 値を扱っているようです。引用符で囲まれた文字列を処理するために拡張する必要がある場合 (たとえば、フィールドにコンマを含めることができるようにするため)、scanf-family は形式のすべての複雑さを処理できないことがわかります。したがって、CSV 形式 (のバリアント) を処理するために特別に設計されたコードを使用する必要があります。

C および C++での「 The Practice of Programming 」で、一連の CSV ライブラリの実装に関する議論を見つけることができます。他にもたくさんあることは間違いありません。

于 2009-10-02T13:26:55.677 に答える
0

同じ質問に対する答えを探してここにたどり着きました。scanf 関数も残したくありませんでした。最後に、自分で zsscanf を作成し、フォーマットを解析し、すべてのデータを 1 つずつ sscanf し、sscanf の戻り値をチェックして、空の読み取りがあるかどうかを確認しました。これはやや私の特殊なケースでした。一部のフィールドのみが必要で、そのうちのいくつかは空である可能性があり、区切り記号を想定できませんでした。

#include <stdarg.h>
#include <stdio.h>

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%...[...]としてセパレーターを必要とし%...[...]ます。そうでない場合は、フォーマット文字列を気にする必要がありました。このように、 のみを気にします%)。

于 2014-01-29T12:11:45.023 に答える
0

正しく動作するように、このコードを少し変更する必要がありました。

//rm token_pure;gcc -Wall -O3 -o token_pure token_pure.c; ./token_pure 
#include <stdio.h>
#include <string.h>

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
于 2014-04-29T20:44:14.790 に答える
0

読み取りをスキップするには、「%」の後に「*」を入力します。さらに、たとえば '%3s' に注意して 3 文字しか読み取ることができません。

于 2012-10-30T05:41:51.113 に答える
-1

scanf() returns the number of items assigned. Maybe you can use that info ...

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));
}

Ugh! And that's only for 1 missing value
As has been pointed out by other answers, you're much better off parsing the string in the 'usual' way: fgets() and strtok().

于 2009-10-02T13:46:27.877 に答える