Search code examples
cstringpathstrtokgetenv

C re-initializing a variable after using strtok


I'm trying to write a function to search in the PATH directories. I used getenv("PATH") to get the path string, then used strtok() to split it.

The second time I call my function it does not reinitialize the path string. it searches only in the first part of the path.

I know that strtok() modifies the original string terminating each token with a \0 . but in my case I don't understand why calling the function again doesn't create a new string with the normal delimiters ":".

Here is my code:

bool str_in_path(char *target, char *result) {
  char *path = getenv("PATH");
  char *token;
  char tokens[10][100];
  int count = 0;
  
  token = strtok(path, ":");
  strcpy(tokens[0], token);
  count++;

  for (int i = 1; token && i < 10; i++) {
    token = strtok(NULL, delimiter);
    if (token) {
      strcpy(tokens[i], token);
      count++;
    }
  }

  for (int i = 0; i < count; i++) {
    struct dirent *entry = NULL
    DIR *dir = opendir(tokens[i]);
    if (dir == NULL) {
      fprintf(stderr, "can't open directory %s\n", tokens[i]);
      continue;
    }   
    while ((entry = readdir(dir)) != NULL) {
      if (!strcmp(target, entry->d_name)) {
        strcpy(result, tokens[i]);
        strcat(result, "/");
        strcat(result, entry->d_name);
        closedir(dir);
        return true;
      }   
    }
    closedir(dir);
  }
  return false;
}

The first time I call the function it behaves as I expect. the second time it searches only the first part of the path. although it calls char *path = getenv("PATH"); to initialize the path string. why is that and how can I solve it.


Solution

  • As mentioned in @chux's comment, strcspn() can be used to parse without modifying the source string.
    Also consider breaking the function into two functions. One to parse the path and the other to open the directories.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    #include <dirent.h>
    
    char **parse_path ( char *path, char *delimiter) {
        char **tokens = NULL;
        char **tmp = NULL;
        char *token = path;
        char *end = path;
        size_t items = 0;
        size_t span = 0;
    
        while ( *token) { // not terminating zero
            end += strcspn ( token, delimiter);
            if ( end == token) {
                break;
            }
            // allocate pointers
            if ( NULL == ( tmp = realloc ( tokens, sizeof *tokens * (items + 2)))) {
                fprintf ( stderr, "problem realloc tokens\n");
                break;
            }
            tokens = tmp;
            span = end - token;
            // allocate memory to a pointer
            if ( NULL == ( tokens[items] = malloc ( span + 1))) {
                fprintf ( stderr, "problem malloc token[items]\n");
                break;
            }
            memmove ( tokens[items], token, span);
            tokens[items][span] = 0; // zero terminate
            ++items;
            tokens[items] = NULL; // sentenal NULL
            if ( *end) { // not terminating zero
                ++end; // advance past one delimiter
            }
            token = end;
        }
    
        return tokens;
    }
    
    bool str_in_path_tokens ( char **tokens, char *target, char *result) {
        while ( tokens && *tokens) {
            struct dirent *entry = NULL;
            DIR *dir = opendir ( *tokens);
            if ( dir == NULL) {
                fprintf(stderr, "can't open directory %s\n", *tokens);
                continue;
            }
            while ( ( entry = readdir ( dir)) != NULL) {
                if ( ! strcmp ( target, entry->d_name)) {
                    strcpy(result, *tokens);
                    strcat(result, "/");
                    strcat(result, entry->d_name);
                    closedir(dir);
                    return true;
                }
            }
            closedir(dir);
            ++tokens;
        }
        return false;
    }
    
    void show_path_tokens ( char **tokens) {
        while ( tokens && *tokens) {
            printf ( "%s\n", *tokens);
            ++tokens;
        }
    }
    
    void free_path_tokens ( char **tokens) {
        while ( tokens && *tokens) {
            free ( *tokens);
            ++tokens;
        }
    }
    
    int main ( void) {
        char *path = getenv ( "PATH");
        char **pathtokens = NULL;
    
        printf ( "%s\n", path);
    
        pathtokens = parse_path ( path, ":");
        show_path_tokens ( pathtokens);
    
        // call
        // str_in_path_tokens ( pathtokens, target, result);
        // as needed with various targets and results
        // no need to reparse the path
    
        free_path_tokens ( pathtokens);
        free ( pathtokens);
    }