I’m trying to build a process logger in C on Linux, but having trouble getting it right. I’d like it to have 3 colums: USER, PID, COMMAND. I’m using the output of ps aux
and trying to dynamically append it to an array. That is, for every line ps aux
outputs, I want to add a row to my array.
This is my code. (To keep the output short, I only grep for sublime. But this could be anything.)
#define _BSD_SOURCE #include <stdio.h> #include <string.h> #include <stdlib.h> int main() { char** processes = NULL; char* substr = NULL; int n_spaces = 0; int columns = 1; char line[1024]; FILE *p; p = popen("ps -eo user,pid,command --sort %cpu | grep sublime", "r"); if(!p) { fprintf(stderr, "Error"); exit(1); } while(fgets(line, sizeof(line) - 1, p)) { puts(line); substr = strtok(line, " "); while(substr != NULL) { processes = realloc(processes, sizeof(char*) * ++n_spaces); if(processes == NULL) exit(-1); processes[n_spaces - 1] = substr; // first column user, second PID, third all the rest if(columns < 2)//if user and PID are already in array, don't split anymore { substr = strtok(NULL, " "); columns++; } else { substr = strtok(NULL, ""); } } columns = 1; } pclose(p); for(int i = 0; i < (n_spaces); i++) printf("processes[%d] = %sn", i, processes[i]); free(processes); return 0; }
The output of the for loop at the end looks like this.
processes[0] = user processes[1] = 7194 processes[2] = /opt/sublime_text/plugin_host 27184 processes[3] = user processes[4] = 7194 processes[5] = /opt/sublime_text/plugin_host 27184 processes[6] = user processes[7] = 27194 processes[8] = /opt/sublime_text/plugin_host 27184 processes[9] = user processes[10] = 27194 processes[11] = /opt/sublime_text/plugin_host 27184
But, from the puts(line)
I get that the array should actually contain this:
user 5016 sh -c ps -eo user,pid,command --sort %cpu | grep sublime user 5018 grep sublime user 27184 /opt/sublime_text/sublime_text user 27194 /opt/sublime_text/plugin_host 27184
So, apparently all the values are being overwritten and I can’t figure out why… (Also, I don’t get where the value 7194
in processes[0] = 7194
and processes[4] = 7194
comes from).
What am I doing wrong here? Is it somehow possible to make the output look like the output of puts(line)
?
Any help would be appreciated!
Advertisement
Answer
The return value of strtok
is a pointer into the string that you tokenise. (The token has been made null-terminated by overwriting the first separator after it with ''
.)
When you read a new line with fgets
, you overwrite the contents of this line and all tokens, not just the ones from the last parsing, point to the actual content of the line. (The pointers to the previous tokens remain valid, but the content at these locations changes.)
The are several ways to fix this.
- You could make the tokens you save arrays of chars and
strcpy
the parsed contents. - You could duplicate the parsed tokens with (the non-standard)
strdup
, which allocates memory for the strings on the heap. - You could read an array of lines, so that the tokens are really unique.