在C语言中将字符拆分为单词

3
我正在将以下格式的行存储到字符中。每个单词都由制表符分隔。
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 来分割字符中的 \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;
    }
  }
}

1
请注意,您应该使用“char array”或“string”,而不是“char” - “将char拆分为单词”具有不同且更明显的含义。 - spinkus
2个回答

3
你使用 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。因此,当读取其他行时,你需要执行以下操作,而不是使用switch语句:
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 */
        }

你在这里做什么? - JoseJ
第三个代码框中,你在 for 循环中使用 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 cipher= 2 0 0 13 54e. WPA2 CCMP PSK xxxx essid= 0 0 13 54e. WPA2 CCMP PSK xxxx - JoseJ

2

一个简单的技巧:

假设你的“单词”中没有任何空格,你可以使用sscanf函数。

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

例如:

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接