How can I make a dummy shell take no arguments in

2019-08-17 04:07发布

问题:

I have a dummy shell program that takes arguments. However, I want it to take no arguments and instead provide a prompt to let the user input the name of executable program and parameters.

For example:

$dummyshell
>(executable program and parameters go here)

Here is the code I have so far:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16

void parseCmdArgs(char *buffer, char** cmdArgs, 
                size_t cmdArgsSize, size_t *nargs)
{
  char *bufCmdArgs[cmdArgsSize]; 
  char **temp;
  char *buf;
  size_t n, p;

  cmdArgs[0], buf, bufCmdArgs[0] = buffer;  

  for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
    if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
      break;
  }

  for (p=n=0; bufCmdArgs[n]!=NULL; n++){
    if(strlen(bufCmdArgs[n])>0)
      cmdArgs[p++]=bufCmdArgs[n];
  }

  *nargs=p;
  cmdArgs[p]=NULL;
}
  int main(int argc, char *argv[], char *envp[]){
  char buffer[BUFFER_SIZE];
  char *args[ARRAY_SIZE];
  char hflag = 'N';
  int *retStatus;
  size_t nargs;
  pid_t pid;

  while(1){

    printf("$dummyshell ");
    fgets(buffer, BUFFER_SIZE, stdin);
    parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs); 


    if (nargs==0)
      continue;

    if (!strcmp(args[0], "help"))
      {
    printf("cat                    cd (absolute path references only\n");
        printf("exit\n");
        printf("help                   history\n");
        printf("jobs                   kill\n");
        printf("ls                     more\n");
        printf("ps                     pwd\n");
    continue;
      }

    if (!strcmp(args[0], "exit" ))
      exit(0);

    pid = fork();

    if (pid){      
      pid = wait(retStatus);
    }

    else {
      if( execvp(args[0], args)) {
    puts(strerror(errno));
    exit(127);
      }
    }

  }    
  return 0;
}

回答1:

What do you think this line does?

cmdArgs[0], buf, bufCmdArgs[0] = buffer;

It actually evaluates the uninitialized cmdArgs[0] and throws it away; then it evaluates the uninitialized buf and throws that away, and finally assigns to bufCmdArgs[0]. If you want to assign to all three variables, use assignment operators in place of the comma operator:

cmdArgs[0] = buf = bufCmdArgs[0] = buffer;

Neither hflag nor the arguments to main() are used; get rid of them (int main(void)). You should #include <sys/wait.h>; you should change retStatus to a simple int and then pass &retStatus to wait(). Failing that, you need to initialize retStatus so it points to an int that can be assigned to.

With those changes, the code runs OK – sort of. It executed ls -l correctly. However, control-D (EOF) to exit failed horribly (I got to see the plain ls listing of my directory quite a lot before I was able to interrupt it.

Separately, you'll also eventually realize that both the commands cd and exit (and probably history too) require special attention; they are shell built-ins for a good reason. That, however, comes later.

Do you know of a way to modify my program so that if the user types a '&' symbol at the end of the program and its parameters, the dummy shell will run the program in the background rather than the foreground?

Yes, I do know of ways to do that. Basically, you don't do the wait() (and when you do wait(), you check whether the dead process you collect information on is the one you expected, or whether it is one of your shell's background processes.


Semi-working code

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16

static void parseCmdArgs(char *buffer, char** cmdArgs, 
                size_t cmdArgsSize, size_t *nargs)
{
    char *bufCmdArgs[cmdArgsSize]; 
    char **temp;
    char *buf;
    size_t n, p;

    cmdArgs[0] = buf = bufCmdArgs[0] = buffer;  

    for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
        if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
            break;
    }

    for (p=n=0; bufCmdArgs[n]!=NULL; n++){
        if(strlen(bufCmdArgs[n])>0)
            cmdArgs[p++]=bufCmdArgs[n];
    }

    *nargs=p;
    cmdArgs[p]=NULL;
}
  //int main(int argc, char *argv[], char *envp[]){
int main(void)
{
    char buffer[BUFFER_SIZE];
    char *args[ARRAY_SIZE];
    int retStatus;
    size_t nargs;
    pid_t pid;

    while(1){

        printf("$dummyshell ");
        fgets(buffer, BUFFER_SIZE, stdin);
        parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs); 


        if (nargs==0)
            continue;

        if (!strcmp(args[0], "help"))
        {
            printf("cat                    cd (absolute path references only\n");
            printf("exit\n");
            printf("help                   history\n");
            printf("jobs                   kill\n");
            printf("ls                     more\n");
            printf("ps                     pwd\n");
            continue;
        }

        if (!strcmp(args[0], "exit" ))
            exit(0);

        pid = fork();

        if (pid){      
            pid = wait(&retStatus);
        }

        else {
            if( execvp(args[0], args)) {
                puts(strerror(errno));
                exit(127);
            }
        }

    }    
    return 0;
}

You might note that error messages should be reported on standard error, not standard output (so puts(strerror(errno)) should be fprintf(stderr, "%s\n", strerror(errno)); because fputs() doesn't add a newline but puts() does).