C strtok解析复杂的字符串

时间:2018-06-06 13:20:31

标签: c string strtok

你好我在C中有一个复杂的问题。我想拆分这个字符串(这个例子我包括所有的陷阱可能性)

 "command1";"sleep 30; command2 -a ; command3";"command4="MyTest""

到:

command1
sleep 30; command2 -a ; command3
command4="MyTest"

字符串是" tab",元素由双引号和;分隔 我如何使用strtok()进行此操作?

我尝试将strtok()与";"像这样的分隔符:

   char mystring[1024]="\"command1\";\"sleep 30; command2 -a ; command3\";\"command4=\"MyTest\"\"";
   token = strtok(mystring, "\";\"");

   while( token != NULL ) {
      error(token);
      token = strtok(NULL, s);
   }

但我有这个输出:

command1
"sleep 30
command2 -a
command3"
"command4="MyTest""

感谢您提前寻求帮助

2 个答案:

答案 0 :(得分:0)

下面的示例根据请求解析输入字符串。它需要进行一些调整才能完全满足您的需求(将printf替换为您的实际处理代码)。

要理解代码,请从描述字符串语法的顶部注释开始。然后转到main - 使用调试器。

一些解释:

  • iss_t是输入字符串流模拟。 iss_get_char使用它来读取输入字符串中的字符。
  • wschar_t用于存储从输入流中读取的字符以及先前的空格。这是必要的,因为您没有指定";之间是否有空格。它由iss_get_wschariss_peek_wschar使用。
  • iss_get_wschar读取空格,直到找到非ws字符。
  • iss_peek_wschar与上述内容相同,但不会从流中提取字符。
  • token_t描述了由流构成的标记。
  • parser_get_token从流中提取令牌。它是perharps最重要的功能。
  • parser_commands从流中提取所有命令,它是解析器的主要功能。
  • parser_command从流中提取命令。

您可以测试代码here

  #include <stdlib.h>
  #include <ctype.h>

  /*
  commands:
    EOF
    |
    command commands

  command:
    command_begin string command_end

  string:
    anything but command_end

  command_begin:
    QUOTE

  command_end:
    QUOTE SEMICOLON
    |
    QUOTE EOF
  */

  struct iss_t
  {
    char* s;
    char* g;
  };
  typedef struct iss_t iss_t;

  char iss_get_char( iss_t* self )
  {
    if ( !*self->g )
      return 0;
    return *self->g++;
  }

  struct wschar_t
  {
    char c;
    char s[ 1024 ];
  };
  typedef struct wschar_t wschar_t;

  char iss_get_wschar( iss_t* self, wschar_t* r )
  {
    char* p = r->s;

    while ( isspace( *p++ = r->c = iss_get_char( self ) ) )
      ;
    *p = 0;
    return r->c;
  }

  char iss_peek_wschar( iss_t* self )
  {
    char* savedg = self->g;
    wschar_t t;
    iss_get_wschar( self, &t );
    self->g = savedg;
    return t.c;
  }

  enum token_t
  {
    tk_done,
    tk_beg,
    tk_end,
    tk_rest
  };
  typedef enum token_t token_t;

  struct parser_t
  {
    iss_t* iss;
    wschar_t wschar;
    token_t token;
  };
  typedef struct parser_t parser_t;

  token_t parser_get_token( parser_t* self )
  {
    iss_get_wschar( self->iss, &self->wschar );

    // if done
    if ( self->wschar.c == 0 )
      return self->token = tk_done;

    // not a quote
    if ( self->wschar.c != '\"' )
      return self->token = tk_rest;

    // it is quote: check the next token
    switch ( iss_peek_wschar( self->iss ) )
    {
    case 0:
    case ';':
      iss_get_wschar( self->iss, &self->wschar ); // eat
      return self->token = tk_end;
    default:
      return self->token = tk_beg;
    }
  }

  int parser_command( parser_t* self )
  {
    printf( "COMMAND: " );
    if ( self->token != tk_beg )
    {
      printf( "command begin expected" );
      return -1;
    }

    while ( parser_get_token( self ) == tk_rest || self->token == tk_beg )
      printf( self->wschar.s );

    if ( self->token != tk_end )
    {
      printf( "command end expected" );
      return -1;
    }

    printf( "\n" );

    return 0;
  }

  void parser_commands( parser_t* self )
  {
    while ( 1 )
    {
      switch ( parser_get_token( self ) )
      {

      case tk_done:
        return;

      default:
        if ( parser_command( self ) < 0 )
          return;

      };
    };
  }


  int main()
  {
    char* s = "\"command1\"; \"sleep 30; command2 -a ; command3\"; \"command4=\"MyTest\"\"";

    iss_t iss = { s, s };
    parser_t parser = { &iss };
    parser_commands( &parser );

    return 0;
  }

答案 1 :(得分:-1)

您必须将字符串设为

char str[]="\"command1\";\"sleep 30; command2 -a ; command3\";\"command4=\"MyTest\"\"";

您无需使用strtok()就可以使用sscanf()

char a[3][100];
sscanf(str, "\"%99[^\"]\";\"%99[^\"]\";\"%99s", a[0], a[1], a[2]);
a[2][strlen(a[2])-1]='\0';
printf("\n%s\n%s\n%s", a[0], a[1], a[2]);

最后a[2]会以这种方式结束"。我们使用a[2][strlen(a[2])-1]='\0';删除它。

\"%[^\"]\"表示读取",读取所有内容,直到另一个"(此部分存储在字符数组中)并读取"

sscanf()中使用宽度说明符来防止缓冲区溢出。

您应该通过检查其返回值(例如

)来检查sscanf()是否成功
if( sscanf(........) != 3)
{
    printf("\nSomething went wrong.");
}

sscanf()返回成功分配的数量,在这种情况下必须为3

输出是:

command1
sleep 30; command2 -a ; command3
command4="MyTest"

编辑:

数组大小100是任意的。如果需要更精确的内存分配,您可以先使用动态内存分配。像

sscanf(str, "\"%*[^\"]%n\";\"%*[^\"]%n\";\"%*s%n", &size[0], &size[1], &size[2]);
printf("\n%d, %d, %d", size[0], size[1], size[2]);
size[2] = size[2]-size[1]-4;
size[1] = size[1]-4;
size[0] = size[0]-1;
char *p[3];
for(int i=0; i<3; ++i)
{
    if( (p[i]=malloc(sizeof(char)*size[i]))==NULL )
    {
        perror("Memory allocation failed!");
        return 1;
    }
}

%n格式说明符返回读取的字符数。请参阅this帖子。

*用于抑制。即,只是丢弃这些值。这个额外的scanf()用于获取字符串的大小。

但这是针对此输入的。