Search code examples
cfgetsstrtok

Odd array behaviour with strtok and fgets


I'm working on a program that acts as a shell interpreter that reads commands with arguments and creates a child that executes the command with execvp(). I'm stuck on doing some string manipulation to collect a char array *args[], particularly with using fgets and strtok.

Here's an MCVE of my code.

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define MAX_LINE 80

int main(void){
    //initialize variables
    char *args[MAX_LINE/2 + 1];
    char input[MAX_LINE];
    //char input[MAX_LINE] = "some sentence unknown"; // <-- this line works fine..

    int counter = 0;
    printf("COMMANDER>");
    fflush(stdout);

    //receive input
    fgets(input,MAX_LINE,stdin);

    //parse input
    char *parser;
    parser = strtok(input," \r\t");

    //parse line
    while(parser != NULL){
                args[counter] = parser;
        counter++;  
        parser = strtok(NULL," ");
    }

    //print results
    int i = 0;
    for(i = 0; i < counter + 1;i++){
        printf("1");
        printf(" - %d: %s\n",i,args[i]);    
    } 

    return 0;
}

The problem here is the ouput. When I try to run this, I get the following output:

COMMANDER>some sentence unknown
1 - 0: some
1 - 1: sentence
1 - 2: unknown

1 - 3: (null)

My issue is that empty space. I can't tell where it's coming from and no matter what I do it appears.

From what I can tell it may be a \n character at the end of the string, or something else, but passing this into execvp as execvp(args[0],args) creates an error, as it interprets this blank line as an argument of " ".

There is a line I've commented out that's just a string assignment at the start of main. If this assignment is used instead of fgets the program works and I get the desired input:

COMMANDER>some sentence unknown
1 - 0: some
1 - 1: sentence
1 - 2: unknown
1 - 3: (null)

Thanks for reading. I'm a bit rusty on my C, so I kept at this on my own for a few hours and still couldn't find a solution.


Solution

  • If you read e.g. this fgets reference you will see that it says

    Parsing stops if end-of-file occurs or a newline character is found, in which case str will contain that newline character.

    [Emphasis mine]

    What you're seeing as "that empty space" is the newline that fgets added at the end of the string.


    There is however a much worse problem in your code.

    With

    char *args[MAX_LINE/2 + 1];
    

    you define an array of pointers, but you leave this array uninitialized. In C uninitialized local (and non-static) variables really are uninitialized. Their contents will be indeterminate and seem almost random.

    More specifically, that you happen to get a null pointer at args[counter] is just pure luck.

    Attempting to use this pointer in any way without initialization will lead to undefined behavior.

    The simple solution is to explicit initialize the array to be full of null pointers:

    char *args[MAX_LINE/2 + 1] = { NULL };
    

    The above will "zero-initialize" all elements, which for pointers mean they will be NULL.