简体   繁体   中英

Writing a C command prompt program with its own 'command line arguments'

Been stuck for hours at this. How do I read in command line arguments in my command prompt program and execute it using execv() system call? The following is the sample output where YWIMC is the prompt.

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

An R command has the syntax R command_path [arg1 to arg4] where they could be 0 to 4 arguments. Eg R /bin/ls OR R /bin/ls -l

I'm supposed to use execv (I'm assuming its better for reading command line arguments since it uses a char array as its parameter and also cos my homework assignment requires me to) but I'm having trouble reading the arguments in.

How do I do this when there are any amount of arguments (0 to 4)? While reading in arguments, how do I make the program recognise that that is the end of all the arguments I gave? (I had a problem where I would add an infinite number of arguments even though I set the max to 4.) The following was my existing code just that I have to change execl to execv

else    {
            result = fork();
            if(result != 0) {   //Parent Code
                childID = wait(&status);
            }
            else    {   //Child Code
                if(strcmp(filePath, "/bin/ls") == 0)    { //specific /bin/ls command
                    execl("/bin/ls", "ls", NULL);
                }
                else    {   //run other programs
                    execl(filePath, NULL);
                }
                return 256;
            }

You can create an array of strings using malloc . As you read in arguments, use realloc to increase the size of the array. Then you can pass the resulting array to execv .

int arglen = 1;
char **args = malloc(arglen * sizeof(char *));
args[0] = strdup(/* program to run */);
while (/* more args to read */) {
    arglen++;
    args = realloc(args, arglen * sizeof(char *));
    args[arglen-1] = strdup(/* next arg */);
}
arglen++;
args = realloc(args, arglen * sizeof(char *));
args[arglen] = NULL;
...
execv(/* prog to call */, args);        

the idea i have in mind is to read file/filepath, take in all arguments, use execv(program,args).

You are on the right track. The exercise not only focuses on the use of execv , but subtly focuses on the command line argument indexes where you must pay attention to the fact that the first argument given on the command line has and index of 1 (eg argv[1] ), while the array needed to hold the arguments to pass to execv will begin at index 0 .

Handling the index offset isn't too difficult, you just have to pay attention to how you fill the array you will send to execv . NOTE : variable initialization is important. The args array you send to execv must have the final pointer be NULL to serve as a sentinel for execv to know where to stop processing arguments. One way to insure you provide a NULL pointer at the end of the array is to initialize ALL pointers to NULL to begin with:

char *args[5] = {NULL};

Then your only task is to insure you only fill the first 4 pointers in the array, leaving the last NULL . Also note , the first pointer in args must contain the full-path to the program you are executing. Your original example above showing R ls will not work unless you append the proper path to the beginning of ls (eg /usr/bin/ls ). How you do that is up to you. With that a short example is:

#include <stdio.h>
#include <unistd.h>

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

    char *args[5] = {NULL};        /* all pointers initialized to NULL */
    int lim = argc < 5 ? argc : 5; /* limit the number of args to read */
    int nargs = lim - 1;           /* adjust the index to fill args    */
    int i;

    /* read the arguments from the command line */
    for (i = 1; i < lim; i++)
        args[i-1] = argv[i];

    /* output the arguments read */
    for (i = 0; i < nargs; i++)
        printf (" args[%d] = %s\n", i, args[i]);

    /* 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: execv failed - path correct?.\n");

    return 0;
}

Compile

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

Use/Output

$ ./bin/execvargs /usr/bin/ls
 args[0] = /usr/bin/ls
3darrayaddr.c                   getline_minimal.c             reallocptr.c
BoggleData.txt                  getline_rdfile.c              rec_char_in_str.c
<snip>

$ ./bin/execvargs /usr/bin/ls -l
 args[0] = /usr/bin/ls
 args[1] = -l
total 7528
-rw-r--r-- 1 david david     376 Sep 23  2014 3darrayaddr.c
-rw-r--r-- 1 david david     192 Jun 27 01:11 BoggleData.txt
-rw-r--r-- 1 david david    3565 Jun 26  2014 DoubleLinkedList-old.c
<snip>

$ ./bin/execvargs my dog has fleas and so does the cat
 args[0] = my
 args[1] = dog
 args[2] = has
 args[3] = fleas
error: execv failed - path correct?.

Looking at execv a little closer

If you would like to see how execv invokes the program you pass as args[0] you can create a small test script in your shell that will echo the arguments it recieves when invoked by execv . Something simple is fine:

#!/bin/bash

declare -i cnt=0

for i in "$@"; do
    printf "  arg[%d] : %s\n" $cnt "$i"
    let cnt=cnt+1
done

Call it test.sh and make it executable. (eg chmod 0755 test.sh ) Then provide ./test.sh along with arguments of your choosing to your program:

$ ./bin/execvargs ./test.sh my dog has fleas
 args[0] = ./test.sh
 args[1] = my
 args[2] = dog
 args[3] = has
  arg[0] : my
  arg[1] : dog
  arg[2] : has

According to the comments, the main problem is NOT the actual execution but the parsing of a command line in your own code. Therefore, I'm just giving the most basic solution here I can think of, heavily commented. It doesn't support anything like quoting, escaping, or editing of the command line by the user, but it should help understand the general concept -- try to use it as a starting point.

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

#define PROMPT "YWIMC > "

/* read in a command */
static char **getCommand(void)
{
    /* static buffers, no need to allocate dynamically for a simple example */
    static char cmdline[1024]; /* command-line */
    static char *command[7];   /* parsed command */

    /* initialize empty command */
    memset(command, 0, sizeof(command));

    do
    {
        /* show prompt: */
        fputs(PROMPT, stdout);

        /* read commandline: */
        if (fgets(cmdline, 1024, stdin))
        {
            int i = 0;

            /* get first token (the command name) */
            if ((command[i++] = strtok(cmdline, " \r\n\t")))
            {
                /* get up to 5 additional tokens (the parameters) */
                while (i < 6 && (command[i++] = strtok(0, " \r\n\t")));
            }
        }
    } while (!(command[0])); /* until something was entered */

    return command;
}

int main(void)
{
    /* endless loop */
    while (1)
    {
        /* get next command */
        char **command = getCommand();

        /* and try to execute it */
        if (!strncmp(command[0], "Q", 1))
        {
            /* quit command */

            puts("k tnx bye.");
            exit(0);
        }
        else if (!strncmp(command[0], "R", 1))
        {
            /* run command */

            /* check there was something to run given: */
            if (!command[1])
            {
                fputs("Command `R': Nothing to run.\n", stderr);
            }
            else
            {
                /* YOUR COMMAND EXECUTION GOES HERE */
                /* (try to create another "static" function for it) */
            }
        }
        else
        {
            int i = 0;

            /* some debugging on unrecognized commands */
            fprintf(stderr, "Unrecognized command: `%s' (args: ",
                    command[i++]);

            while (command[i])
            {
                fprintf(stderr, "`%s' ", command[i++]);
            }
            fputs(")\n", stderr);
        }
    }
}

A key issue to understand is why command has 7 entries. The first one is reserved for your command. the last one should always be NULL (or 0 ) so your code can detect the end of the arguments list. Leaves 5 usable parameters. If one of your command should run the first of it's parameters as an external command and allow 4 additional arguments, you need exactly those 5.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM