Reputation: 31
I am storing lines of the following format into a char. Each word is separated by a tabulation.
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
I want to split each line (contained in a char) in order to get the fields BSSID, CH, CIPHER and ESSID. My final goal is to store the fields of each line in an array of chars to work more comfortably with them. Something like this:
char fields[] = { BSSID, CH,CIPHER, ESSID}
Now i am using strtok, in order to split the \t
of the char, but this is very uncomfortable. Following here it is my first approach, but is very poor becuase it only focus on the fourth line and second field. Can anybody help me with code? I am also opened to a different way of programming it.
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;
}
}
}
Upvotes: 3
Views: 121
Reputation: 63481
Your approach with strtok
is fine, but maybe you want to store the data into a struct. Something like the following. I've chosen to have fixed string maximum lengths, and have just invented what those might be.
struct row_data {
char bssid[18];
char ch[4];
char cipher[10];
char essid[20];
};
If you always know exactly which order the columns are in, you can just about stop here. Just index the columns with an enumeration:
enum column_id {
COL_RSSID = 0,
COL_CH = 5,
COL_CIPHER = 8,
COL_ESSID = 10
};
And now something like this would do it:
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 );
}
It's not too much extra work to also make a target_length
or similar which could be used as a parameter to strncpy
(my example is just short, and uses strcpy
). Or you could go a different direction and store only pointers in the struct. Then you can use dynamic allocation to copy the strings in.
Now, if your column order is not known, you'll have to abstract this one step further. That would be first reading the header row and looking for the parts you're interested in, and storing the column index they appear at. This would make your code more complex, but not unreasonably so.
A starting point might be this (requires <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;
}
}
}
You can see where this is going. Assuming you had read the header into header
, now you have populated columns
with column indices of each column you're interested in. And so when reading the other rows you do this instead of the 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) );
}
}
}
Instead of storing offsets in the table, you could of course store a pointer, much like we did with target
in the switch statement. But that would require pointing directly at something like &row.rssid
. Maybe that's enough for you (I suspect I've already provided more than enough).
But to be fair, I'll point out this option, which might be simpler than using memcpy
as above. And I'll roll in the strncpy
stuff I keep avoiding.
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 */
}
Upvotes: 3
Reputation: 2251
A simple trick:
Considering that your "words" do not have any blank space within them, you can use sscanf
.
This function will allow you to read values from a string instead of the stdin
. These are automatically parsed as separate values if they have any whitespace between them. You may ignore the values you do not want to read.
Example:
sscanf(token, "%s %*s %*s %*s %*s %s %*s %*s %s %*s %s",BSSID, CH, CIPHER, ESSID);
%*s
will read a field but not assign it to any variable. So, only the needed fields will be assigned to the variables.
You have to run this statement for every line in the output.
Upvotes: 2