Home > Net >  Using strtok_s, the second item is always NULL, even though the first works right. How can I get bot
Using strtok_s, the second item is always NULL, even though the first works right. How can I get bot

Time:12-28

I am working in C and the strtok_s function isnt working as expected. I want to separate 2 halves of user input, delimited by a space character between them. Ive been reading the manual but i cannot figure it out. Below is the function I wrote. Its goal is to separate the first and second half of user input delimited by a space and return the value to 2 pointers. The print statement has only been used for my debugging.

void argGetter(char* commandDesired, char** firstArg, char** secondArg) {
    // this char holds the first part of the command before the " "
    char* commandCleanDesired;
    // this char array holds the part after the " "
    char *nextToken;
    char *argument;
    commandCleanDesired = strtok_s(commandDesired, " ", &nextToken);
    argument = strtok_s(NULL, " ", &nextToken);
    printf("\n\nCMD 1 is %s\n\nCMD 2 is %s\n\n\n", commandCleanDesired, argument);
    *firstArg = commandCleanDesired;
    *secondArg = argument;

}

//this shows how argGetter is called.

void main() {
// these hold the return values from argGetter() 
    char* secondArg = NULL;
    char* firstArg = NULL;
//This holds user input
    char commandDesired[255];

    //This line prints the prompt
    printf("\n\tSanity$hell> ");
    //Then we get user input
    scanf_s("%s", commandDesired, 255);

    
    
    //split the command from args using argGetter
    argGetter(commandDesired, &firstArg, &secondArg);
    printf("\n First Arg is %s\n", firstArg);
    printf("\nYour second arg is %s\n\n", secondArg);

}

It gets commandCleanDesired fine, but the second variable, (named 'argument') is ALWAYS null.

I have tried the things below to get the value after the space and store it in argument (unsuccessfully). These little code snippets show how I modified the above code during my attempts to solve the issue.

commandCleanDesired = strtok_s(commandDesired, " ", &commandDesired);
argument = strtok_s(commandDesired, " ", &commandDesired);
//the above resulted in NULL for the second value argument as well.
// Below is the next thing i tried.
char * nextToken;
commandCleanDesired = strtok_s(commandDesired, " ", &nextToken);
argument = strtok_s(NULL, " ", &nextToken);
//both result in argument being NULL. 
//I tried the above after reading the manual more.

I have been reading the manual at https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strtok-s-strtok-s-l-wcstok-s-wcstok-s-l-mbstok-s-mbstok-s-l?view=msvc-170. I used NULL for the string argument the second time because the above manual led me to believe that was necessary for all subsequent calls after the first call. An example input of commandDesired would be "cd C://" For the above input, i would like this function to have commandCleanDesired = 'cd' and argument = 'C://' currently with the misbehavior of the above function for the above input, the function gives commandCleanDesired = 'cd' and argument = (NULL)

TLDR, How am I misusing the strtok_s function in C, how can I get the second value after the space to be stored in the "argument" pointer?

Thank you in advance.

CodePudding user response:

The issue is that I used scanf_s or scanf to get the user input in main. This tokenizes the input, which is not what I want.

If you want to read a whole line, use fgets. When I use fgets instead, the issue is solved!

CodePudding user response:

If you want to separate strings at the space characters, don't use scanf() (or friends) with the %s format specifier, as it stops reading at space characters themselves, so the string that finally reaches strtok (or friends) don't have spaces on it. This is probably the most probable reason (I have not looked in detail at your code, sorry) that you get the first word in the first time, and NULL later.

A good alternative, is to use fgets(), in something like:

    char line[1024];
    /* the following call to fgets() reads a complete line (incl. the
     * \n char) into line. */
    while (fgets(line, sizeof line, stdin)) {  /* != NULL means not eof */
        for (   char *arg = strtok(line, " \t\n");
                arg != NULL;
                arg = strtok(NULL, " \t\n"))
        {
             /*process argument in arg here */
        }
    }

Or, if you want to first get out the last \n char, and then process the whole line to tokenize the arguments...

    char line[1024];
    /* the following call to fgets() reads a complete line (incl. the
     * \n char) into line. */
    while (fgets(line, sizeof line, stdin)) {  /* != NULL means not eof */
         process_line(strtok(line, "\n"));  /* only one \n at end can be, at most */
    }

Then, inside the process_line() function you need to check the parameter for NULL (for the case the string only has a single \n on it, that will result in a null output from strtok())

IMPORTANT WARNING: strtok() is not reentrant, and also it cannot be nested. It uses an internal, global iterator that is initialized each time you provide a first non-null parameter. If you need to run several levels of scanning, you have two options:

  • run the outer loop in full, appending work to do to a second level set of jobs (or similar) to be able to run strtok() on each separate level when the first loop is finished.
  • run the reentrant version of strtok(), e.g. strtok_r(). This will allow reentrancy and nesting, you just need to provide a different state buffer (where strtok stores the iterator state) for each nesting level (or thread)
  • Related