0

I am struggling with taking trying to use tokens from strtok as an input.

I want to be able to input "cd .." and have the first token be checked if it falls into the if-else condition and if it does use the second token as a parameter for chdir().

At the else-if statement, it currently ignores the code underneath. So I am unable to use chdir().

Example input: cd ..

This is what I have tried so far.

int main(){


    char input[256];
    while(1) {
        fgets(input, 256, stdin);
        char * token = strtok(input, " ");
        while (token!= NULL){
            if (strncmp(token, "exit", 4) == 0){
                exit(1);
            }
            else if (strncmp(token, "cd", 2) == 0) {
                token = strtok(NULL, " ");
                chdir(token);
            }
            else{
                printf("Command not found.\n");
                break;
            }
        }
    }
}

How would I be able to use the second token from strtok()? Any recommendations on where to go from here?

tadman
  • 208,517
  • 23
  • 234
  • 262
CJF
  • 3
  • 2
  • BTW: `token = strtok(NULL, " "); chdir(token); }` is not good as `token` may be NULL when calling `chdir` – Support Ukraine Oct 12 '22 at 01:38
  • @SupportUkraine good point about the strcmp(). My question was how would I use the second token from input `..` as a parameter in chdir(). Right now the "else if" does not run the code underneath it. Since my input is "cd ..", I strtok() the input but am unable to use the second token. – CJF Oct 12 '22 at 01:49
  • So the problem is to get the second token for `chdir` ? If so please edit the question and add that explanation – Support Ukraine Oct 12 '22 at 01:52
  • A `for` loop works fine, e.g. `for (char *token = strtok(input, " "); token; token = strtok(NULL, " \n")) { /* handle the tokens */ }` If you want a counter for the tokens, declare `int n = 0;` before the loop and then just use the `comma operator` to append `, i++` as part of the loop increment. – David C. Rankin Oct 12 '22 at 02:18

3 Answers3

2

I replaced chdir() with a printf() as the parent process will retain it's own current working directory. This way you can see it working.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char input[] = "cd ..\n";
    char *command = strtok(input, "\n ");
    if(!strcmp(command, "cd")) {
        char *path = strtok(NULL, "\n ");
        if(!path) path = getenv("HOME");
        printf("cd %s\n", path);
    } else if(!strcmp(command, "exit"))
        return 1;
    else {
        printf("Command not found.\n");
        return 0;
    }
}

and the output is:

cd ..

You used fgets() in your original code, which will likely return the string with a trailing newline. You can remove it (see @SupportUkraine's answer), or as I did above treat it as just another delimiter.

If you don't want to use $HOME, you can use Get home directory in Linux.

You might want to use getopt() to parse a command line like this. It will make it easier if you want any of your commands to have options.

Allan Wind
  • 23,068
  • 5
  • 28
  • 38
1

Here's an illustration of how one might "dissect" a "command line".

#include <stdio.h>
#include <string.h>

int main() {
    char buf[ 256 ];

    strcpy( buf, "./a.out foo bar foobar barfoo\n" ); // simulate fgets()

    int ac = 0;
    char *av[ 32 ]; // Or make it dynamic (a minor challenge)
    for( char *cp = buf; ( av[ ac ] = strtok( cp, " \n" ) ) != NULL; cp = NULL, ac++ )
         ;

    for( int i = 0; i < ac; i++ )
        printf( "%d: %s\n", i, av[i] );
    printf( "And on the end of the array: %p\n", (void*)av[ac] );

    return 0;
}
0: ./a.out
1: foo
2: bar
3: foobar
4: barfoo
And on the end of the array: // some form of system dependent NULL

Although daunting to some, that single, long-ish for() loop, with its single invocation of strtok() assigns NULL to the last element of the array (just like the big boys do with argc/argv[].)

Do one thing at a time. Don't mix-and-match by each item in your 'decoding' to do the work of fetching its own parameters.

Fe2O3
  • 6,077
  • 2
  • 4
  • 20
  • I mildly disagree with your advise to not mix-and-match. You may want to bail rather than parse the remaining string if something goes wrong. If you are using `strtok()` to parse a larger language, you usually only care about the 1 or 2 next tokens and may want to stream the tokens. With the array you also have to navigate it with an index variable if what you are parsing has options. None of this is probably new to you so more for the benefits of others that might be seduced to the dark side :-) What your answer does point is that strtok() modifies what it parses (nasty side effect). – Allan Wind Oct 12 '22 at 02:55
  • @AllanWind All very good points. Thank you for adding them! Truth is, `strtok()` is a bit of a sledgehammer solution... Perhaps there is an expectation to allow single/double quotes to _bundle_ terms into one 'token'... Where do we go when "cat file|filter" is wanted? And, "cat file | filter"... One strives to do one's best, doesn't one? `:-)` – Fe2O3 Oct 12 '22 at 03:57
  • OT: "Although daunting to some...." Well, for stuff like this I prefer a `while` like https://ideone.com/Gn8sLq but it can be done with a `for` loop like: `for (av[ac] = strtok(buf, " \n" ); av[ac] && (av[++ac] = strtok(NULL, " \n" )); );` Then you don't need `cp` and the repeated `cp = NULL` – Support Ukraine Oct 12 '22 at 04:08
  • @SupportUkraine Trading one throwaway pointer for two invocations of the function call... Hmmm... Many ways to skin a cat! `:-)` (And, my compiler would warn "assignment within conditional" for that syntax... It, like me, is old and cranky and wants to see some boolean or it gets stroppy... `:-)` – Fe2O3 Oct 12 '22 at 04:13
  • There is no extra invocations of `strtok`. Just the initialization with one set of arguments and then the "running" with a NULL argument. For the warning: Strange that you get a warning. My gcc using `gcc -Wall -Wextra -pedantic ...` doesn't generate any warnings. Try: `for (av[ac] = strtok(buf, " \n" ); av[ac]; av[++ac] = strtok(NULL, " \n" ) );` – Support Ukraine Oct 12 '22 at 04:23
  • @SupportUkraine Thanks... I'll stick with what's above... There are variations, of course. Cheers! (Thank you for the other helpful "clean up" comments! :-) – Fe2O3 Oct 12 '22 at 05:24
1

To me it seems that your problem is that

fgets(input, 256, stdin);

will (normally) give you a string input that has a trailing newline. In other words: If the user types "cd .." and press ENTER then the input is "cd ..\n" which leads to the tokens "cd" and "..\n". The second token is not what you want as argument for chdir.

In general the best solution is to remove that trailing newline just after fgets.

This Removing trailing newline character from fgets() input propose a solution which is very popular on SO:

fgets(input, 256, stdin);
input[strcspn(input, "\n")] = 0;  // Remove trailing newline if present

I prefer

fgets(input, 256, stdin);
size_t len = strlen(input);
if (len > 0 && input[len-1] == '\n') input[len-1] = '\0'; // Remove trailing newline if present

but that's a "matter of taste".

BTW: Once you have done this, you can stop using strncmp and instead use strcmp for an exact match. By doing that input like "exitxxx" will no longer be the same as "exit"

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63