Well, I’m trying to write a shell for linux using C. Using the functions fork() and execl(), I can execute each command, but now I’m stuck trying to read the arguments:
char * command;
char ** c_args = NULL;
bytes_read = getline (&command, &nbytes, stdin);
command = strtok(command, "\n ");
int arg = 0;
c_arg = strtok(NULL, "\n ");
while( c_arg != NULL ) {
if( c_args == NULL ) {
c_args = (char**) malloc(sizeof(char*));
}
else {
c_args = (char**) realloc( c_args, sizeof(char*) * (arg + 1) );
}
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
c_arg = strtok(NULL, "\n ");
arg++;
}
...
pid_t pid = fork()
...
...
execl( <path>, command, c_args, NULL)
...
...
That way I get errors from the command when I try to pass arguments, for example:
ls -l
Gives me:
ls: cannot access p��: No such file or directory
I know that the problem is the c_args allocation. What’s wrong with it?
Cheers.
You can’t use
execl()for a variable list of arguments; you need to useexecv()or one of its variants (execve(),execvp(), etc). You can only useexecl()when you know all the arguments that will be present at compile time. In most cases, a general shell won’t know that. An exception is when you do something like:Here, you’re invoking the shell to run a single string as the command line (with no other arguments). However, when you’re dealing with what people type at the keyboard in a full shell, you won’t have the luxury of knowing how many arguments they typed at compile time.
At its simplest, you should be using:
The zeroth argument, the command name, should be what you pass to
execvp(). If that’s a simple file name (no/), then it will look for the command in directories on your$PATHenvironment variable. If the command name contains a slash, then it will look for the (relative or absolute) file name specified and execute that if it exists, and fail if it does not. The other arguments should all be in the null-terminated listc_args.Now, there may also be other memory allocation issues; I’ve not scrutinized the code. You could check them, though, by diagnostic printing of the argument list:
That prints each argument on a separate line. Note that it doesn’t stop until it encounters a null pointer; it is crucial that you null terminate your list of pointers to the argument strings.
This bit of your code:
looks like overkill in the usual case, and an inadequate memory allocation in the extreme cases. When you’re copying strings around, allocate enough length. I see that you’re using
strtok()to bust apart a string — it’ll do for the early incarnations of a shell, but when you get to process command lines likels -l>$tmp, you will findstrtok()‘s penchant for trampling over your delimiter before you get to read it becomes a major liability. However, while you’re using it, you probably don’t have to copy the arguments like that; you can just setc_args[arg++] = result_from_strtok;. When you do need to copy, you should probably usestrdup(); it doesn’t forget to allocate enough space for the trailing'\0', for example, and neither over-allocates nor under-allocates.