从子进程运行命令

时间:2015-11-12 22:13:08

标签: c fork execvp waitpid

我想创建一个shell,其中子进程运行linux命令(在execvp的帮助下),如“ls”等。我也希望能够运行带有“ls -a”或“ls”等参数的命令ls -l / tmp“ 父必须等待子进程使用“waitpid”执行给定的命令。 当我试图用“ls -a”运行shell时,它将它作为2个单独的命令。输出:ls $ -a $

#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"

int main(int argc, char **argv) 
{
    char input[64];
    pid_t pid;
    char *status;
    char *args[64];
    char **next = args;
    char *temp;

    while (1) {
        printf("$");
        fgets(input,"exit",stdin);
        if (strcmp(input, "exit") == 0) {
        exit(0)
        }
        pid = fork();

        if (pid == 0) {
            //child

            *temp = strtok(input, " ");


            while (temp != NULL) {
                *next++ = temp;
                *temp = strtok(NULL, " ");
            }


            if (execvp(args[0], args) == -1) {
                perror("Execvp Error");
            }
            exit(0);

        }else if (pid < 0) {
            printf("error during fork");
        }else {
            //parent
            waitpid(pid, &status, 0);
        }
    }
}

2 个答案:

答案 0 :(得分:0)

如果编写一些辅助函数并提出合适的数据结构,那就变得微不足道了。例如:

main.c

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stringlist.h"
#include "io.h"


int main(void)
{
    StringList list = NULL;
    get_input(&list);

    while ( strcmp(stringlist_string_at_index(list, 0), "exit") ) {
        pid_t p = fork();

        if ( p < 0 ) {
            perror("fork() error");
            exit(EXIT_FAILURE);
        }
        else if ( p == 0 ) {
            char ** args = stringlist_raw_list(list);
            execvp(args[0], args);
            switch ( errno ) {
                case EACCES:
                    printf("Error: access denied.\n");
                    break;

                case ENOENT:
                    printf("Error: file not found.\n");
                    break;

                default:
                    printf("Error: couldn't fulfill request.\n");
                    break;
            }
            exit(EXIT_FAILURE);
        }
        else {
            int status;
            waitpid(p, &status, 0);
        }

        get_input(&list);
    }

    stringlist_destroy(list);
    return EXIT_SUCCESS;
}

包含帮助文件:

io.h

#ifndef IO_H
#define IO_H

#include "stringlist.h"

void get_input(StringList * list);

#endif      /*  IO_H  */

io.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "io.h"
#include "stringlist.h"


#define MAX_INPUT_LENGTH 256

static void get_input_line(char * buffer, const size_t buffer_size);
static void output_prompt(void);
static StringList tokenize_input(void);


/*  Prompts for and gets input from standard input.
 *
 *  If the StringList pointed to by `list` is not NULL, it will
 *  be destroyed. The StringList pointed to by `list` will be
 *  modified to point to a new StringList created from the input.
 *  If no input is entered, function will prompt for it again.
 *
 *  Note: the input is tokenized purely by space characters, so input
 *  resembling:
 *
 *      cat "Filename with spaces"
 *
 *  will return four tokens, not two. This simple method of tokenizing
 *  will be unsuitable for many applications.
 */

void get_input(StringList * list)
{
    if ( *list ) {
        stringlist_destroy(*list);
    }

    do {
        output_prompt();
        *list = tokenize_input();
    } while ( stringlist_length(*list) == 0 );
}


/*  Gets a line of input from standard input.
 *
 *  Function strips the trailing newline, if present, and
 *  exits the program on error.
 */

static void get_input_line(char * buffer, const size_t buffer_size)
{
    if ( !fgets(buffer, buffer_size, stdin) ) {
        fprintf(stderr, "error getting input\n");
        exit(EXIT_FAILURE);
    }

    const size_t len = strlen(buffer);
    if ( len > 0 && buffer[len - 1] == '\n' ) {
        buffer[len - 1] = 0;
    }
}


/*  Outputs the shell prompt  */

static void output_prompt(void)
{
    printf("shell$ ");
    fflush(stdout);
}


/*  Gets a line of input from standard input and tokenizes it  */

static StringList tokenize_input(void)
{
    StringList list = stringlist_create();

    char input[MAX_INPUT_LENGTH];
    get_input_line(input, MAX_INPUT_LENGTH);

    char * t = strtok(input, " ");
    while ( t ) {
        stringlist_add(list, t);
        t = strtok(NULL, " ");
    }

    return list;
}

stringlist.h

#ifndef STRING_LIST_H
#define STRING_LIST_H

#include <stddef.h>
#include <stdbool.h>

typedef struct stringlist * StringList;

StringList stringlist_create(void);
bool stringlist_delete_last(StringList list);
void stringlist_destroy(StringList list);
size_t stringlist_length(StringList list);
char * stringlist_string_at_index(StringList list, const size_t index);
char ** stringlist_raw_list(StringList list);
void stringlist_add(StringList list, const char * str);

#endif      /*  STRING_LIST_H  */

stringlist.c

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "stringlist.h"

#define DEFAULT_LIST_SIZE 8

struct stringlist {
    char ** list;       /*  Pointer to list of strings      */
    size_t size;        /*  Current capacity of list        */
    size_t top;         /*  Lowest empty element of list    */
};


/*  Creates a new list of default capacity  */

StringList stringlist_create(void)
{
    struct stringlist * new_list = malloc(sizeof *new_list);
    if ( !new_list ) {
        perror("memory allocation failed");
        exit(EXIT_FAILURE);
    }

    new_list->size = DEFAULT_LIST_SIZE;
    new_list->top = 0;

    char ** list = calloc(new_list->size, sizeof *list);
    if ( !list ) {
        perror("memory allocation failed");
        exit(EXIT_FAILURE);
    }

    new_list->list = list;

    return new_list;
}


/*  Deletes the last string in the list.
 *
 *  Returns false if the list was empty, otherwise true.
 */

bool stringlist_delete_last(StringList list)
{
    if ( list->top ) {
        list->top -= 1;
        free(list->list[list->top]);
        list->list[list->top] = NULL;
        return true;
    }
    return false;
}


/*  Destroys the list and frees all resources  */

void stringlist_destroy(StringList list)
{
    while ( stringlist_delete_last(list) ) {
        ;
    }
    free(list->list);
    free(list);
}


/*  Returns the number of strings currently in the list  */

size_t stringlist_length(StringList list)
{
    return list->top;
}


/*  Returns the string at the specified index of the list  */

char * stringlist_string_at_index(StringList list, const size_t index)
{
    return list->list[index];
}


/*  Returns a pointer to the raw list of strings.
 *
 *  This raw list will be NULL-terminated, that is, if the raw
 *  list contains `length` strings, then raw_list[length] == NULL.
 *  This makes the raw list suitable for passing, for instance, to
 *  execv() and friends.
 */

char ** stringlist_raw_list(StringList list)
{
    return list->list;
}


/*  Adds a string to the list.
 *
 *  The raw list will be dynamically resized, if necessary.
 */

void stringlist_add(StringList list, const char * str)
{
    if ( list->top + 1 >= list->size ) {
        char ** new_array = realloc(list->list,
                                    list->size * 2 * sizeof *new_array);
        if ( !new_array ) {
            perror("memory allocation failed");
            exit(EXIT_FAILURE);
        }
        list->list = new_array;
        list->size *= 2;
    }

    char * duped = strdup(str);
    if ( !duped ) {
        perror("memory allocation failed");
        exit(EXIT_FAILURE);
    }

    list->list[list->top] = duped;
    list->top += 1;
    list->list[list->top] = NULL;
}

这会为空输入添加一些错误检查,并以更有意义的方式响应失败的execvp()调用。

示例会话:

Paul@Pauls-iMac:~/Documents/src/sandbox/simple_shell$ ./ss
shell$ 
shell$ 
shell$ ./Fakefile
Error: file not found.
shell$ ./Makefile
Error: access denied.
shell$ ls -alF
total 96
drwxr-xr-x  12 Paul  staff    408 Nov 12 21:18 ./
drwxr-xr-x   6 Paul  staff    204 Nov 12 20:42 ../
-rw-r--r--   1 Paul  staff    368 Nov 12 21:07 Makefile
-rw-r--r--   1 Paul  staff   2016 Nov 12 21:18 io.c
-rw-r--r--   1 Paul  staff    113 Nov 12 21:10 io.h
-rw-r--r--   1 Paul  staff   2240 Nov 12 21:18 io.o
-rw-r--r--   1 Paul  staff   1214 Nov 12 21:08 main.c
-rw-r--r--   1 Paul  staff   1608 Nov 12 21:11 main.o
-rwxr-xr-x   1 Paul  staff  10032 Nov 12 21:18 ss*
-rw-r--r--   1 Paul  staff   2799 Nov 12 20:52 stringlist.c
-rw-r--r--   1 Paul  staff    504 Nov 12 20:53 stringlist.h
-rw-r--r--   1 Paul  staff   2492 Nov 12 21:11 stringlist.o
shell$ ps
  PID TTY           TIME CMD
75221 ttys002    0:00.19 -bash
75678 ttys002    0:00.00 ./ss
shell$ echo Hello, world!
Hello, world!
shell$ cat "Tokenizing input with spaces is generally bad.txt"
cat: "Tokenizing: No such file or directory
cat: input: No such file or directory
cat: with: No such file or directory
cat: spaces: No such file or directory
cat: is: No such file or directory
cat: generally: No such file or directory
cat: bad.txt": No such file or directory
shell$ exit
Paul@Pauls-iMac:~/Documents/src/sandbox/simple_shell$ 

答案 1 :(得分:-2)

我认为purkavlos在这里给了我们一个示例代码(它还没有完全正常运行),以便我们向他展示通过子进程运行命令的逻辑...只是纠正部分代码并不是很有帮助..

解决方案: 通过使用gets()而不是scanf()将解决您的第一个问题,以便您可以接受带有空格的字符串,如Paul所说。 检查是否适合您,因为我可以看到标记化是正确的。 之后命令应该运行但我不确定输出,你是通过孩子得到的吗?