-2

I've been looking every sscanf post here and I can't find an exact solution suitable for my problem. I was implementing my own Shell and one of the characteristics is that if I find the dollar sign $, I got to replace what is exactly behind with the environmental variable:

cd $HOME should actually be replaced by cd /home/user before I even execute the cd.

My question is what is the code to use sscanf to take out the dollar sign and simply get HOME on the same string? I've been struggling with some null pointers trying this:

char * change;
if (strcmp(argv[1][0],'$')==0){
                    
   change = malloc(strlen(argv[y]));
   sscanf(argv2[y]+1,"%[_a-zA-Z0-9]",change);
   argv2[y]=getenv(change);
}

But this seems to be failing, I'm having a segmentation fault core. (If needed i put more code, my question is specially focused on the sscanf). Quick explanation argv is an array of pointers to the lines entered and parsed, so actually the content of argv[0] = "cd" and argv[1]="$HOME". I also know that the variable I'm going to receive after the $ has the format %[_a-zA-Z0-9].

Please ignore the non failure treatment.

Wolf
  • 9,679
  • 7
  • 62
  • 108
Román
  • 136
  • 1
  • 9
  • Put only the amount of code needed to reproduce the problem, but make sure that it can compile. – S.S. Anne Nov 29 '19 at 22:03
  • this could help: https://stackoverflow.com/questions/1902681/expand-file-names-that-have-environment-variables-in-their-path – Jean-François Fabre Nov 29 '19 at 22:04
  • 3
    `strcmp(argv[1][0],'$')` compares *character strings* not individual *characters*. Invoking a function that requires a *nul-terminated* string without providing a *nul-terminated* string results in *Undefined Behavior*. – David C. Rankin Nov 29 '19 at 22:04
  • @DavidRankin-ReinstateMonica and so can I use ````==```` operator with characters? – Román Nov 29 '19 at 22:08
  • 2
    Yes, just `if (argv[1][0] == '$')` – David C. Rankin Nov 29 '19 at 22:09
  • ¡Perfect! Thank you very much. For further questioning, is malloc really needed here? – Román Nov 29 '19 at 22:15
  • No, not really. So long as you know what your longest argument will be, you can just use an array with *automatic storage duration*. `char change[1024];` should be fine. (you have at least a Megabyte of stack space, using 1% for a fixed buffer is fine) The general rule is simply *Don't Skimp on Buffer Size!*. So if your longest argument is 40-chars, a buffer of 128-chars is fine. A buffer size of 1-4K is fairly common (except on embedded microcontrollers where storage is at a premium) – David C. Rankin Nov 29 '19 at 22:18
  • You also have your indexes a bit confused (unless your program is named `cd`). `argv[0]` is always the *Program Name with absolute path*. `argv[1]` is your first *command-line argument*, `argv[2]` the second, etc... – David C. Rankin Nov 29 '19 at 22:23

2 Answers2

2

You asked "is malloc() necessary" in your code snipped and the answer was "no", you could use a simple array. In reality, if you are simply making use of the return of getenv() without modification in the same scope without any other calls to getenv(), all you need is a pointer. getenv() will return a pointer to the value part of the name=value pair within the program environment. However the pointer may be a pointer to a statically allocated array, so any other calls to getenv() before you make use of the pointer can cause the text to change. Also, do not modify the string returned by getenv() or you will be modifying the environment of the process.

That said, for your simple case, you could do something similar to:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */

int main (int argc, char **argv) {

    char *envp = NULL,      /* pointer for return of getenv() */
        buf[MAXC];          /* buffer to parse argv[2] w/sscanf */

    if (argc < 3) { /* validate at least 2 program arguments given */
        printf ("usage: %s cmd path\n", strrchr (argv[0], '/') + 1);
        return 1;
    }

    if (*argv[2] == '$')    /* chest 1st char of argv[2] == '$' */
        if (sscanf (argv[2] + 1, "%1023[_a-zA-Z0-9]", buf) != 1) {
            fputs ("error: invalid format following '$'.\n", stderr);
            return 1;
        }

    if (!(envp = getenv (buf))) {   /* get environment var from name in buf */
        fprintf (stderr, "'%s' not found in environment.\n", buf);
        return 1;
    }

    printf ("%s %s\n", argv[1], envp);  /* output resulting command line */
}

Right now the program just outputs what the resulting command line would be after retrieving the environment variable. You can adjust and build the array of pointers for execv as needed.

Example Use/Output

$ ./bin/getenvhome "cd" '$HOME'
cd /home/david

Look things over and let me know if you have any further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • I feel bad for the -1, because it's very understandable, but you missed the question being asked. The `argv` in their question is **not** what they will be receiving in their `main`, it is the `argv` that you pass to something like the `execve` system call. – mtraceur Nov 30 '19 at 00:07
  • You actually answer what they care about later in the answer, so it would be a +1 from me were it not for the above misunderstanding and the couple leading paragraphs that resulted from it before getting to the answer. – mtraceur Nov 30 '19 at 00:14
  • To be clear I was the -1, and have now switched it to a +1. – mtraceur Nov 30 '19 at 00:44
  • No worries. I generally take the approach on situations like this to drop a comment and say "Hey dummy, you misread the question...". It's just an approach I take of "being helpful" rather than "being critical" when what happened was a clear misunderstanding. Thank you for clearing that up. – David C. Rankin Nov 30 '19 at 00:45
  • ¡That was very helpful! I changed to ````sscanf (argv[2] + 1, "%1023[_a-zA-Z0-9]", buf)```` and seems working nice. I have one more question, my intent is to enter back the value of ````buf```` into ````argv2```` because as someone pointed out is not the same as the ````argv```` passed to the main function, it is actually a structure that was given to us by the proffessor with the variables received from parsing the lines entered in our own minishell: Quick explanation argv is an array of pointers to the lines entered and parsed, so actually the content of argv[0] = "cd" and argv[1]="$HOME" – Román Dec 01 '19 at 11:01
  • Yes, that was pointed out. No worries. The words "my shell" hinted at it. But this is also a good real-world example of why having two different variables named `argv` may lead to confusion as `argv` is the second parameter to `main`defined by the C-standard. Good luck with your coding. – David C. Rankin Dec 01 '19 at 21:40
1

You don't need sscanf here, you can just slide the pointer.

If argv[1] points to the string "$HOME", then argv[1] + 1 points to "HOME", so your example code would just become:

char * change;
if (argv[y][0] == '$')
{
    change = argv[y] + 1;
}

(But in this case your variable should not be named change. Call your variables what they represent, for example in this case variable_name, because it contains the name of the shell variable you will be expanding - remember your code is for communicating to other humans your intent and other helpful information about the code.)

To be clear, whether you do sscanf or this trick, you still have to do error checking to make sure the variable name is actually the right characters.

Remember that sscanf won't tell you if there are wrong characters, it'll just stop - if the user writes a variable like $FO)O (because they made a typo while trying to type $FOO) sscanf will just scan out the first valid characters and ignore the invalid ones, and return FO instead.

In this case ignoring bad data at the end would be bad because user interfaces (that includes shells) should minimize the chances that a user mistake silently does an unintended wrong thing.

mtraceur
  • 3,254
  • 24
  • 33
  • Good approach here. The only justification for `sscanf (...` would be if the argument could contain characters other than what is included in the character class. If that's not a possibility, then your approach is all that is needed. I agree with the naming comment as well. As you show above, you could simply use `getenv (argv[y] + 1);` and save the additional pointer. – David C. Rankin Nov 30 '19 at 00:47
  • @DavidRankin-ReinstateMonica Doesn't `sscanf` just stop parsing upon the first wrong character instead of indicating an error? So I think either approach requires error handling. (See edit.) – mtraceur Nov 30 '19 at 02:29
  • Yes. On the first mismatch between a *conversion specifier* and the input a *matching-failure* occurs and character extraction from the buffer ceases. You are correct. Validation is required See [Henry Spencer's 10 Commandments for C Programmers - No. 6](http://www.seebs.net/c/10com.html) ... *We be warned...* – David C. Rankin Nov 30 '19 at 02:35