【问题标题】:Split a char into words in C将一个字符拆分为C中的单词
【发布时间】:2016-12-09 08:26:32
【问题描述】:

我将以下格式的行存储到一个字符中。每个单词都由一个表格分隔。

BSSID              PWR  Beacons    #Data, #/s  CH  MB   ENC  CIPHER AUTH ESSID
00:34:34:34:34:34  -56        9        0    0  11  54e. WPA2 CCMP   PSK  wifi_id 
00:44:44:44:44:34  -56        9        0    0  11  54e. WPA2 CCMP   PSK  wifi_id2
00:54:54:54:54:54  -56        9        0    0  11  54e. WPA2 CCMP   PSK  wifi_id3  

我想拆分每一行(包含在一个字符中)以获取字段 BSSID、CH、CIPHER 和 ESSID。我的最终目标是将每一行的字段存储在一个字符数组中,以便更舒适地使用它们。像这样的:

char fields[] = { BSSID, CH,CIPHER, ESSID}

现在我正在使用strtok,为了分割char的\t,但这很不舒服。以下是我的第一种方法,但很差,因为它只关注第四行和第二个字段。有人可以帮我写代码吗?我也对另一种编程方式持开放态度。

const char s[2]= "\t";
while (fgets(path, sizeof(path)-1, fp) != NULL) {
  i = i + 1;
  if (i == 4){
    token = strtok(path, s);
    /* walk through other tokens */
    while( token != NULL )
    {
      token = strtok(NULL, s);
      strncpy(field2, token, 18);
      break;
    }
  }
}

【问题讨论】:

  • 请注意,您的意思是“char array”或“string”而不是“char”——“splitting a char into words”有不同的更明显的含义。

标签: c linux string


【解决方案1】:

您使用strtok 的方法很好,但也许您想将数据存储到结构中。类似于以下内容。我选择了固定的字符串最大长度,并且刚刚发明了这些长度。

struct row_data {
    char bssid[18];
    char ch[4];
    char cipher[10];
    char essid[20];
};

如果您始终确切地知道列的顺序,您可以在这里停下来。只需使用枚举索引列:

enum column_id {
    COL_RSSID = 0,
    COL_CH = 5,
    COL_CIPHER = 8,
    COL_ESSID = 10
};

现在像这样可以做到这一点:

int column = 0;
char *target = NULL;
struct row_data row;
struct row_data empty_row = {0};

while( fgets(path, sizeof(path), fp) )
{
    row = empty_row;

    token = strtok(path, s);
    for( column = 0; token; token = strtok(NULL,s), column++ )
    {
        switch( column )
        {
        case COL_RSSID:  target = row.rssid;  break;
        case COL_CH:     target = row.ch;     break;
        case COL_CIPHER: target = row.cipher; break;
        case COL_ESSID:  target = row.essid;  break;
        default:         target = NULL;
        }

        if( target ) strcpy(target, token);
    }

    /* do something with row */
    printf( "Read rssid=%s ch=%s cipher=%s essid=%s\n",
            row.rssid, row.ch, row.cipher, row.essid );
}

创建一个target_length 或类似的可以用作strncpy 的参数并不需要太多额外的工作(我的例子很短,使用strcpy)。或者你可以换个方向,只在结构中存储指针。然后就可以使用动态分配把字符串拷贝进去了。

现在,如果您的列顺序未知,您将不得不进一步抽象这一步。这将首先阅读标题行并查找您感兴趣的部分,并存储它们出现的列索引。这会使您的代码更加复杂,但并非不合理。

起点可能是这样(需要<stdlib.h>):

struct column_map {
    const char * name;
    size_t offset;
    int index;
} columns = {
    { "RSSID",  offsetof( struct row_data, rssid ),  -1 },
    { "CH",     offsetof( struct row_data, ch ),     -1 },
    { "CIPHER", offsetof( struct row_data, cipher ), -1 },
    { "ESSID",  offsetof( struct row_data, essid ),  -1 },
    { NULL }
};

/* first read the header */
token = strtok(header, s);
for( column = 0; token; token = strtok(NULL,s), column++ )
{
    for( struct column_map *map = columns; map->name; map++ ) {
        if( map->index == -1 && 0 == strcmp(token, map->name) ) {
            map->index = column;
        }
    }
}

你可以看到这是怎么回事。假设您已将标题读入header,现在您已经使用您感兴趣的每一列的列索引填充了columns。因此,在读取其他行时,您这样做而不是切换:

row = empty_row;
token = strtok(path, s);
for( column = 0; token; token = strtok(NULL,s), column++ )
{
    for( struct column_map *map = columns; map->name; map++ ) {
        if( map->index == column ) {
            /* again, if using strncpy, store a length inside the map,
               and use MIN(map->length, strlen(token)+1) or similar    */
            memcpy( (char*)&row + map->offset, token, strlen(token) );
        }
    }
}

您当然可以存储一个指针,而不是在表中存储偏移量,就像我们在 switch 语句中使用 target 所做的那样。但这需要直接指向&row.rssid 之类的东西。也许这对你来说已经足够了(我怀疑我已经提供了足够多的东西)。

但公平地说,我会指出这个选项,它可能比使用上面的memcpy 更简单。我会加入我一直避免的strncpy

struct row_data row;

struct column_map {
    const char * name;
    char *target;
    size_t target_size;
    int index;
} columns = {
    { "RSSID",  row.rssid,  sizeof(row.rssid),  -1 },
    { "CH",     row.ch,     sizeof(row.ch),     -1 },
    { "CIPHER", row.cipher, sizeof(row.cipher), -1 },
    { "ESSID",  row.essid,  sizeof(row.essid),  -1 },
    { NULL }
};


/* ::: */


        if( map->index == column ) {
            strncpy( map->target, token, map->target_size );
            map->target[map->target_size-1] = '\0';   /* in case of overflow */
        }

【讨论】:

  • 你在这里做什么:
  • 在第三个代码框中,你在 for with token = strtok(NULL,s) 里面做什么?如果我使用您编写的代码直到第三个框代码,并且没有得到预期的结果。相反,在 printf 的每个参数中,我只得到分割线的 \t 。类似于:rssid=98:FC:11:A8:7B:67 -64 2 0 0 13 54e。 WPA2 CCMP PSK xxxx ch= -64 2 0 0 13 54e。 WPA2 CCMP PSK xxxx 密码 = 2 0 0 13 54e。 WPA2 CCMP PSK xxxx essid= 0 0 13 54e。 WPA2 CCMP PSK xxxx
【解决方案2】:

一个简单的技巧:

考虑到您的“单词”中没有任何空格,您可以使用sscanf

此函数将允许您从字符串而不是stdin 中读取值。如果它们之间有任何空格,它们将被自动解析为单独的值。您可以忽略不想读取的值。

例子:

sscanf(token, "%s %*s %*s %*s %*s %s %*s %*s %s %*s %s",BSSID, CH, CIPHER, ESSID);

%*s 将读取一个字段但不将其分配给任何变量。因此,只有需要的字段才会分配给变量。

您必须为输出中的每一行运行此语句。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    • 1970-01-01
    • 1970-01-01
    • 2019-03-12
    相关资源
    最近更新 更多