Question

I am to program a simple shell in C for my project that can implement environment variables. I looked up on how to use the getenv, setenv, putenv. So far so good i've tried to use the getenv to show the shell variables...well... with some succes . But I have a feeling that my reasoning is flawed. I have a char** argv which contains parsed input from the user input. I now check if argv starts with the command "echo" and then if any of the following inputs starts with a $ sign or not. Here's my code:

int executeVariables(char** arguments){
    int i = 0;
    if(strcmp(arguments[i], "echo") == 0){
        char *variable;
        for(i = 1; arguments[i] != NULL; i++){
            char *str = arguments[i];
            if( *(str + 0) == '$'){
                variable = getenv(str + 1);
            }else{
                variable = getenv(str);
            }
            if(!variable){
                //puts("not a variable");
                printf("%s ", arguments[i]);
            }else{
                //puts("a variable");
                printf("%s ", variable);
            }
        }
        printf("\n");
        exit(0);
    }

    return 1;

}

I think that normal linux shell finds the $ sign, it expands the variable before invoking the echo command. My shell isn't following this principle, it's expanding variables inside the echo command itself. Any idea as to how I can implement this? Thanks.

EDIT:

A problem I have is: echo $HOME and echo HOME gives me the same result which is wrong.

EDIT:

After various tests everything works well. But to really test it i'll need to create a local variable then echo this value. I tried it using putenv function but it doesn't create the local variable.

i = 0;
char** temp = malloc(sizeof (*temp));
if(strstr(userInput, "=") != NULL){
    //puts("we got equals");
    puts(userInput);
    if(putenv(userInput) == 0){
       printf("doing putenv(%s)\n", userInput);
        exit(0);
    }
    else{
        puts("couldnt putenv");
       exit(1);
    }
}

userInput: char *userInput is the input gotten from the command line using fgets()

Was it helpful?

Solution

You're specifically asking the code to do getenv() for the string, even if $ isn't found. That's why it will lookup $HOME or HOME. Just remove the else case for not finding the dollar-sign, and make sure to initialize variable to NULL at its declaration, and put it inside the loop.

Something like so:

// First, perform replacements
int executeVariables(char** arguments){
    int i = 0;

    for(i = 0; arguments[i] != NULL; i++){
        char *str = arguments[i];

        if(*str == '$'){
            // make sure the result isn't NULL, though I'm not sure a real shell does
            char *tmp = getenv(str + 1);
            if (tmp != NULL) {
                arguments[i] = getenv(str + 1); // save off the argument
            }
        }
    }

    // Then actually execute the function. This would be like bash's echo builtin
    if (strcmp(arguments[0], "echo") == 0) {
        int i;
        for (i = 1; arguments[i] != NULL; i++) {
            printf("%s ", arguments[i]);
        }
        printf("\n");
    }

    // Other functions could go here

    return 1;
}

Edit: As far as your methodology goes, why do you specifically check for echo? Why not make it generic, and check all the arguments, including the first one? You actually probably want to substitute all the potential environment variables, so if you had MYECHO=echo somewhere, the following would work. To make it more generic, you'd have this function, which would then execute the stuff based on the expanded variables. You could make it all nice and have separate functions, but to fit it all in here, I've updated the code above accordingly, though I haven't tested it. ;)

Edit: That being said, werewindle's comment about doing this earlier does apply -- replace the arguments, like here, and then use that updated arguments array to have a separate function do whatever it needs to do. :)

> $MYVAR totally awesome $HOME
totally awesome /home/user

Edit: As for the putenv() situation, you'll want something structured like the following. This way, it will set it for the shell, and any other processes you run in the shell.

void do_args_env(char *args[])
{
    // do putenv, etc.
}

// inside main loop in shell process
while (1) { // just an example
    if (check_args_syntax(args) != 0) {
        // error
    }

    do_args_env(args);

    // fork and do other stuff
}

(Hopefully final) Edit: As an explanation, processes generally don't (perhaps can't?) affect the environment of processes above them in their hierarchy; only the other way around. So if you putenv() in a child, that child's siblings (i.e. other processes forked from its parent) won't get the environment change.

Glad I could be of help!

OTHER TIPS

Yes, normal shells expand variables during parsing command line. So you need substitute variables in function, that produces array that "contains parsed input from the user input". In this case code for echo (and other commands) will be much shorter.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top