Writing an interpreter or more like a command prom

2019-01-29 08:40发布

问题:

I'm supposed to write an interpreter program which is more like a command prompt. This is some background info:

General flow of basic interpreter
1. Prompt for user request.
2. Carry out the user request.
3. Unless user terminates the program, go to step 1.

Run a command. Format:
R command_path [arg1 to arg4]
//a single character ‘R’ which stands for 'Run', followed by the path of the command
//the user can supply up to 4 command line arguments
Behavior:
a. If command exist, run the command in a child process with the supplied
command line arguments
 Wait until the child process is done
b. Else print error message “XXXX not found”, where XXXX is the user
entered command

E.g.

YWIMC > R /bin/ls
a.out ex2.c ...... //output from the “ls” command
YWIMC > R /bin/ls –l //same as executing “ls –l”
total 144
-rwx------ 1 sooyj compsc 8548 Aug 13 12:06 a.out
-rwx------ 1 sooyj compsc 6388 Aug 13 11:36 alarmClock
.................... //other files not shown

the idea i have so far is, read in file path, read in arguments, use execv(filePath, args). however, i can't get the loop and the syntax right.

while(scanf("%s", args[i]) !=0)  { //read in arguments
     i++;
     }
execv(filePath,args);

this reads an infinite number of arguments. as i said i can't get the syntax right. processing strings in C is such a pain :(

Re-edited. This is my current code which is rather incomplete

#include <stdio.h>
#include <fcntl.h>      //For stat()
#include <sys/types.h>   
#include <sys/stat.h>
#include <unistd.h>     //for fork(), wait()

int main()
{
    char request, filePath[100];
    int result, pathExist, childID, status;
    struct stat buf;

    //read user input
    printf("YWIMC > ");
    scanf("%c", &request);
    while (request != 'Q'){ //if 'Q' then just exit program
        // Handle 'R' request
        scanf("%s", &filePath); //Read the filePath/program name
        pathExist = stat(filePath, &buf);  
        if(pathExist  < 0)  {   
            printf("%s not found\n", filePath);
        }
        else    {
            result = fork();
            if(result != 0) {   //Parent Code
                childID = wait(&status);
            }
            else    {   //Child Code
                if(strcmp(filePath, "/bin/ls") == 0)    {
                    execl("/bin/ls", "ls", NULL); //change to execv
                }                                 //with the use of a 2D
                                                  //string array
                else    {  //same for this
                    execl(filePath, NULL);
                }
                return 256;
            }
        }


        fflush(stdin);      //remove all left over inputs

        printf("YWIMC > ");
        scanf("%c", &request);
    }

    printf("Goodbye!\n");
    return 0;

}

回答1:

OK, I think I finally understand what it is you want to do. To create your YWIMC interpreter environment and be able to handle both command not found and errors in the execution with the command passed to execv as well as returning to the YWIMC after a successful command, you have to fork the call to execv to a separate process to prevent execv from terminating the program.

Basically, you create the YWIMC shell with an outer while loop displaying the prompt and reading the command line (cmdline) on each iteration. Within the loop, the easiest way to separate the command line is with strtok or strsep (strtok) used below. You can make that process simple by calling strtok in a for loop:

    for (p = strtok (line, " \n"); 
            p && i < MAXA - 1;
            p = strtok (NULL, " \n"))
        cmdline[i++] = strdup (p);

Note: strtok modifies the original string, so you must make a copy of the string to be able to provide the error message containing the original command line.

If the users enters a request R as the first token and it has at least one additional argument, you fork the process. In the child (pid == 0) you pass the proper /path/to/command and arguments to execv. The easiest way to pass the proper command and arguments is to create a pointer args that points to the second element of cmdline (so that R is effectively discarded from the array passed to execv). You also need to provide a way for the child process to exit in the event a bad command is passed to execv (otherwise the user will wonder why they must enter q twice to exit after a bad command is passed.)

At that point, it is up to execv to execute the command, or fail with an error provided on failure. When the users enter q the shell will need to free all memory allocated by strdup and then exit.

Try the following and let me know if I finally understood what you were attempting to do. Let me know if you have any questions.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

/* MAXC - maximum characters that can be entered by user at prompt
   MAXA - maximum arguments to read (R + path + 1 2 3 4 + NULL) */
#define MAXC 256
#define MAXA 7

void trimcrnl (char *s);

int main (void) {

    char line[MAXC] = {0};

    printf ("\n Entering 'YWIMC' environment\n"
            "   usage: R command_path [arg1 to arg4]\n"
            "          ('q' to quit).\n\n");

    while (printf ("YWIMC > ") &&
        fgets (line, MAXC, stdin)) 
    {
        if (*line == 'q') break;
        if (*line != 'R') continue;

        trimcrnl (line);  /* strip newline from end of line */

        char *p = line;
        char *cmdline[MAXA] = {NULL};
        char **args = &cmdline[1];
        char *err = strdup (line);
        int i = 0, nargs = 0, status = 0;
        pid_t pid;

        /* parse line into separate tokens (arguments) */
        for (p = strtok (line, " \n"); 
                p && i < MAXA - 1;
                p = strtok (NULL, " \n"))
            cmdline[i++] = strdup (p);

        nargs = i;  /* save the number of arguments found */

        if (nargs < 2) continue;

#ifdef DEBUG
        /* output command line & arguments read */
        for (i = 0; i < nargs; i++)
            printf (" cmdline[%d] = %s\n", i, cmdline[i]);
        for (i = 0; i < nargs - 1; i++)
            printf (" args[%d] = %s\n", i, args[i]);
#endif
        if ((pid = fork ()) == -1) {
            fprintf (stderr, "error: fork failed returning -1.\n");
            exit (EXIT_FAILURE);
        }

        if (pid == 0) { /* child process */
            /* call execv (NOTE: you must provide a full-path
               to the program being executed, e.g. /usr/bin/ls)
            */
            if (execv (args[0], args) == -1)
                fprintf (stderr, "error: '%s' not found.\n", err);
            _exit (EXIT_FAILURE);
        }

        waitpid (pid, &status, 0);
    }

    return 0;
}

/* strip newline or carriage return from string 's' */
void trimcrnl (char *s)
{
    if (!s) return;

    size_t len = strlen (s);
    while (len > 0 && (s[len-1] == '\n' || s[len-1] == '\r'))
        s[--len] = 0;
}

Compile

$ gcc -Wall -Wextra -o bin/execvargs execvargs.c

or to compile with debug output enabled:

$ gcc -Wall -Wextra -o bin/execvargs execvargs.c -DDEBUG

Use/Output

$ ./bin/execvargsfile

 Entering 'YWIMC' environment
   usage: R command_path [arg1 to arg4]
          ('q' to quit).

YWIMC > anything not starting with 'R'
YWIMC > R badpath badcommand
error: 'R badpath badcommand' not found.
YWIMC > R /bin/ls /home/david/cnf/LVM
Lvm.pdf
YWIMC > R /bin/ls -al /home/david/cnf/LVM
total 380
drwxr-xr-x  2 david david   4096 May 21 22:22 .
drwxr-xr-x 41 david david   4096 Aug 27 17:58 ..
-rw-r--r--  1 david david 380862 May 21 22:22 Lvm.pdf
YWIMC > q