首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C中的另一个shell

C中的另一个shell
EN

Code Review用户
提问于 2018-04-12 22:18:16
回答 2查看 1.5K关注 0票数 18

大约一个学期前,我用C语言写了这个,用于一项关于操作系统的大学作业。虽然我在这次任务中得了10分,但我怀疑这是值得的-

  • 管道实现可以写得更好。
  • 在有些情况下(1%),我把管道卡住了,而bash没有。
  • 也许有些函数可以分解成更小/更简单的函数。
  • 调试消息(尽管它们是可选的)可以写入日志文件中。

我们不允许将源代码分割成不同的文件。

这是它的源代码。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

struct subcommand_t {
    char **argument; // Array of arguments
    size_t size;     // Number of arguments
};

struct command_t {
    struct subcommand_t *subcommand; // Array of subcommands.
    size_t size;                     // Number of subcommands.
};

struct line_t {
    struct command_t *command; // Array of commands.
    size_t size;               // Number of commands.
};

void prompt(char *prompt) {
    #ifdef DEBUG
    fprintf(stderr, "[PROMPT] Displaying prompt\n");
    #endif
    fprintf(stdout, "%s ", prompt);
}

char *reader(size_t size){
    char *buffer;
    #ifdef DEBUG
    fprintf(stderr, "[READER] Allocating memory for buffer [size: %zu]\n", size);
    #endif
    buffer = malloc(sizeof(char) * size);
    if (buffer == NULL) exit(EXIT_FAILURE);
    #ifdef DEBUG
    fprintf(stderr, "[READER] Reading to buffer\n");
    #endif
    int character;
    size_t length = 0;
    while (EOF != (character = fgetc(stdin)) && character != '\n') {
        buffer[length++] = (char) character;
        if (length == size) {
            #ifdef DEBUG
            fprintf(stderr, "[READER] Reallocating memory for buffer [size: %zu]\n", size);
            #endif
            buffer = realloc(buffer, sizeof(char) * (size += 32));
            if (buffer == NULL) exit(EXIT_FAILURE);
        }
    }
    #ifdef DEBUG
    fprintf(stderr, "[READER] Setting the NULL terminator for buffer\n");
    #endif
    buffer[length++] = '\0';
    return realloc(buffer, sizeof(char) * length);
}

void executor(struct line_t line) {
    for (size_t i = 0; i < line.size; ++i) {
        #ifdef DEBUG
        fprintf(stderr, "[EXECUTOR] Executing line.command[%zu]\n", i);
        #endif
        int previous;
        for (size_t j = 0; j < line.command[i].size; ++j) {
            #ifdef DEBUG
            fprintf(stderr, "[EXECUTOR] Executing line.command[%zu].subcommand[%zu]\n", i, j);
            #endif
            int p[2];
            pipe(p);
            if (line.command[i].subcommand[j].size != 0) {
                if (strcmp(line.command[i].subcommand[j].argument[0], "exit") == 0) {
                    #ifdef DEBUG
                    fprintf(stderr, "[EXECUTOR] Exiting shell\n");
                    #endif
                    exit(EXIT_SUCCESS);
                } else if (strcmp(line.command[i].subcommand[j].argument[0], "cd") == 0) {
                    #ifdef DEBUG
                    fprintf(stderr, "[EXECUTOR] Changing directory to %s\n", line.command[i].subcommand[j].argument[1]);
                    #endif
                    chdir(line.command[i].subcommand[j].argument[1]);
                } else {
                    switch(fork()) {
                        case -1:
                            #ifdef DEBUG
                            fprintf(stderr, "[EXECUTOR] Failed to fork a child process\n");
                            #endif
                            exit(EXIT_FAILURE);
                        case  0:
                            #ifdef DEBUG
                            fprintf(stderr, "[EXECUTOR] Running on a child process [pid: %d]\n", getpid());
                            #endif
                            if (j == 0) {
                                dup2(0, STDIN_FILENO);
                            } else {
                                dup2(previous, STDIN_FILENO);
                            }
                            close(p[0]);
                            if (j+1 < line.command[i].size) {
                                dup2(p[1],STDOUT_FILENO);
                            }
                            close(p[0]);
                            execvp(line.command[i].subcommand[j].argument[0], line.command[i].subcommand[j].argument);
                            exit(EXIT_FAILURE);
                        default:
                            #ifdef DEBUG
                            fprintf(stderr, "[EXECUTOR] Running on the parent process [%d]\n", getpid());
                            #endif
                            previous=p[0];
                            close(p[1]);
                    }
                }
            }
        }
        #ifdef DEBUG
        fprintf(stderr, "[EXECUTOR] Waiting for any terminated child processes\n");
        #endif
        while(wait(NULL) > 0) {
            #ifdef DEBUG
            fprintf(stderr, "[EXECUTOR] Found a terminated child process\n");
            #endif
        }
    }
}

struct line_t parser(char *buffer, char *del1, char *del2, char *del3) {
    size_t i, j, k;
    char *str1, *str2, *str3;
    char *token1, *token2, *token3;
    char *saveptr1, *saveptr2, *saveptr3;

    struct line_t line;
    line.size=0;
    line.command=NULL;
    for (i = 0, str1 = buffer ;; i++, str1 = NULL) {
        token1 = strtok_r(str1, del1, &saveptr1);
        if (token1 == NULL) break;
        line.size++;
        if (i == 0) {
            #ifdef DEBUG
            fprintf(stderr, "[PARSER] Allocating memory for line.command [size: %zu]\n", line.size);
            #endif
            line.command = malloc(sizeof(struct command_t));
        } else {
            #ifdef DEBUG
            fprintf(stderr, "[PARSER] Reallocating memory for line.command [size: %zu]\n", line.size);
            #endif
            line.command = realloc(line.command, line.size * sizeof(struct command_t));
        }
        line.command[i].size=0;
        line.command[i].subcommand=NULL;
        for (j = 0, str2 = token1 ;; j++, str2 = NULL) {
            token2 = strtok_r(str2, del2, &saveptr2);
            if (token2 == NULL) break;
            line.command[i].size++;
            if (j == 0) {
                #ifdef DEBUG
                fprintf(stderr, "[PARSER] Allocating memory for line.command[%zu].subcommand [size: %zu]\n", i, line.command[i].size);
                #endif
                line.command[i].subcommand = malloc(sizeof(struct subcommand_t));
            } else {
                #ifdef DEBUG
                fprintf(stderr, "[PARSER] Reallocating memory for line.command[%zu].subcommand [size: %zu]\n", i, line.command[i].size);
                #endif
                line.command[i].subcommand = realloc(line.command[i].subcommand, line.command[i].size * sizeof(struct subcommand_t));
            }
            line.command[i].subcommand[j].size=0;
            line.command[i].subcommand[j].argument=NULL;
            for (k = 0, str3 = token2 ;; k++, str3 = NULL) {
                token3 = strtok_r(str3, del3, &saveptr3);
                if (token3 == NULL) break;
                line.command[i].subcommand[j].size++;
                if (k == 0) {
                    #ifdef DEBUG
                    fprintf(stderr, "[PARSER] Allocating memory for line.command[%zu].subcommand[%zu].argument [size: %zu]\n", i, j, line.command[i].subcommand[j].size);
                    #endif
                    line.command[i].subcommand[j].argument = malloc(sizeof(char *));
                } else {
                    #ifdef DEBUG
                    fprintf(stderr, "[PARSER] Reallocating memory for line.command[%zu].subcommand[%zu].argument [size: %zu]\n", i, j, line.command[i].subcommand[j].size);
                    #endif
                    line.command[i].subcommand[j].argument = realloc(line.command[i].subcommand[j].argument, (line.command[i].subcommand[j].size + 1) * sizeof(char *));
                }
                line.command[i].subcommand[j].argument[k] = malloc((strlen(token3)+1) * sizeof(char));
                memset(line.command[i].subcommand[j].argument[k], 0, strlen(token3)+1);
                strcpy(line.command[i].subcommand[j].argument[k], token3);
            }
            if (line.command[i].subcommand[j].size != 0) {
                #ifdef DEBUG
                fprintf(stderr, "[PARSER] Setting the NULL terminator for line.command[%zu].subcommand[%zu]\n", i, j);
                #endif
                line.command[i].subcommand[j].argument[line.command[i].subcommand[j].size] = NULL;
            }
        }
    }
    return line;
}

int main() {
    while (1) {
        prompt("$");
        char *buffer = reader(1024);
        struct line_t line = parser(buffer, ";", "|", " \t");
        executor(line);
    }
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2018-04-13 05:00:09

你写了一个漂亮而简单的外壳。它适用于非常简单的命令,但对于更复杂的命令则失败(详见下文)。代码可以在几个地方清除。

要使您的代码被其他人很好地读懂,让自动格式化程序来处理缩进和间距。如果有可用的GNU缩进,请使用以下命令行:

代码语言:javascript
复制
indent --k-and-r-style --no-tabs --line-length 200 --case-indentation 4 --braces-on-func-def-line shell.c

而不是每次都写这个:

代码语言:javascript
复制
#ifdef DEBUG
fprintf(stderr, "[READER] Allocating memory for buffer [size: %zu]\n", size);
#endif

您应该定义一个宏DEBUG_PRINTF:

代码语言:javascript
复制
#ifdef DEBUG
#define DEBUG_PRINTF(...) fprintf(stderr, __VA_ARGS__)
#else
#define DEBUG_PRINTF(...) (void)0
#endif

然后你就可以写:

代码语言:javascript
复制
DEBUG_PRINTF("[READER] Allocating memory for buffer [size: %zu]\n", size);
代码语言:javascript
复制
if (buffer == NULL) exit(EXIT_FAILURE);

在退出错误代码之前,应打印一条错误消息。按照惯例,空输出意味着成功。

启用所有编译器警告并正确修复它们。对于GCC来说,这是-Wall -Wextra -Werror -O2

  • 而不是void prompt,而是编写static void prompt。这使得该函数成为当前文件的本地函数,并避免了冲突,以防另一个文件定义同名的另一个函数。(也适用于其他职能)
  • 而不是int main(),编写int main(void)来修复“丢失的原型”警告。
  • 将所有只读字符串声明为const char *而不是char *。这会影响promptparser的参数。

重命名你的职能。函数名通常是动词。您当前的名称是executorparser等。这些名称是可读的和可以理解的,但仍然应该是executeparse

对于reader来说,这更困难,因为有一个叫做read的系统提供的函数。因此,应该将其重命名为read_line

在需要变量的地方直接声明变量。而不是:

代码语言:javascript
复制
char *buffer;
buffer = malloc(sizeof(char) * size);

只需写:

代码语言:javascript
复制
char *buffer = malloc(sizeof(char) * size);

由于sizeof(char)被定义为始终为1,所以不要使用它:

代码语言:javascript
复制
char *buffer = malloc(size);

executor函数中,不要到处重复line.command[i].subcommand[j],而是定义一个新变量:

代码语言:javascript
复制
for (size_t j = 0; j < line.command[i].size; ++j) {
    subcommand_t subcommand = line.command[i].subcommand[j];

    if (subcommand.size != 0) {
        // ...

parser函数当前意外地解析了以下命令:

代码语言:javascript
复制
echo ";"

它输出",但没有给出为什么分号和第二个引号没有回显的任何提示。

代码语言:javascript
复制
memset(line.command[i].subcommand[j].argument[k], 0, strlen(token3)+1);
strcpy(line.command[i].subcommand[j].argument[k], token3);

memset是多余的,应该删除。

mallocrealloc一起分配的任何内存必须在与free一起使用后释放。

当我在您的shell中按下Ctrl+D时,我会陷入无穷无尽的循环中。所有其他shell都会在这一点上退出(或者指示我必须输入exit,而不是只输入Ctrl+D,这真的很烦人)。

要处理这种情况,请在reader函数中添加错误处理,如果返回值为NULL,则从main返回。

票数 21
EN

Code Review用户

发布于 2018-04-13 13:35:43

使用_t

您可能需要重新考虑在类型名称中使用_t结尾。简而言之,大多数标准类型名称都使用这种方法,一般的做法是不使用用户定义的类型。

引用POSIX:

为了允许实现者提供自己的类型,所有符合标准的应用程序都需要避免以"_t“结尾的符号,这允许实现者提供其他类型。

作为一个基本例子:

代码语言:javascript
复制
typedef struct Subcommand {
    char **argument; // Array of arguments
    size_t size;     // Number of arguments
} Subcommand;

在这个堆栈溢出问题中还有更多的信息可能是有帮助的。

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/191914

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档