支持管道、重定向、*匹配的miniShell
阅读原文时间:2023年07月08日阅读:2

先上成果图

源代码

仅供技术点的分享,抄袭者就算了,所以main.c就不贴了

/*
 * split_line.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mini_shell.h"

#define is_delim(x) ((x)==' '||(x)=='\t')

char *new_str(char *s, int l) {
    char *rv = emalloc(l + 1);
    rv[l] = '\0';
    strncpy(rv, s, l);
    return rv;
}

// 返回shell中获取到的一行命令
char *next_cmd(char *prompt, FILE *fp) {
    char *buf;                /* the buffer        */
    int buf_size = 0;            /* total size        */
    int pos = 0;            /* current position    */
    int c;                /* input char        */

    printf("%s", prompt);                /* prompt user    */
    while ((c = getc(fp)) != EOF) {

        /* need space? */
        if (pos + 1 >= buf_size) {        /* 1 for \0    */
            if (buf_size == 0) buf = emalloc(BUFSIZ);    /* y: 1st time    */
            else buf = erealloc(buf, buf_size + BUFSIZ);       /* or expand    */
            buf_size += BUFSIZ;        /* update size    */
        }

        /* end of command? */
        if (c == '\n') break;

        /* no, add to buffer */
        buf[pos++] = c;
    }
    if (c == EOF && pos == 0) return NULL;              /* EOF and no input    */
    buf[pos] = '\0';
    return buf;
}

// 将每个句子依空格再次拆分
char **split_sentence(char *line) {
    char **args;
    int spots;            /* spots in table    */
    int buf_space;            /* bytes in table    */
    int arg_num = 0;            /* slots used        */
    char *cp = line;            /* pos in string    */
    char *start;
    int len;

    if (line == NULL)            /* handle special case    */
        return NULL;

    args = emalloc(BUFSIZ);        /* initialize array    */
    buf_space = BUFSIZ;
    spots = BUFSIZ / sizeof(char *);

    while (*cp != '\0') {
        while (is_delim(*cp))        /* skip leading spaces    */
            cp++;
        if (*cp == '\0')        /* quit at end-o-string    */
            break;

        /* make sure the array has room (+1 for NULL) */
        if (arg_num + 1 >= spots) {
            args = erealloc(args, buf_space + BUFSIZ);
            buf_space += BUFSIZ;
            spots += (BUFSIZ / sizeof(char *));
        }

        /* mark start, then find end of word */
        start = cp;
        len = 1;
        while (*++cp != '\0' && !(is_delim(*cp))) len++;
        args[arg_num++] = new_str(start, len);
    }
    args[arg_num] = NULL;
    return args;
}

// 依管道符将命令拆分为几个句子,如果没有管道符,则只有一个句子
char ***split_line(char *line) {
    if (!line) return NULL;
    char ***orders = emalloc(BUFSIZ);
    int buf_space = BUFSIZ;
    int spots = BUFSIZ / sizeof(char **);
    int sen_num = 0;
    char *p = line;
    do {
        while (is_delim(*p))        /* skip leading spaces    */
            p++;
        if (*p == '\0')        /* quit at end-o-string    */
            break;
        if (sen_num + 1 >= spots) {
            orders = erealloc(orders, buf_space + BUFSIZ);
            buf_space += BUFSIZ;
            spots += BUFSIZ / sizeof(char **);
        }

        char *end = p;
        while (*end != '\0' && *end != '|') end++;
        char *sentence = emalloc(end - p + 1);   // 由管道符隔开的为一个sentence
        strncpy(sentence, p, end - p);
        sentence[end - p] = 0;
        orders[sen_num++] = split_sentence(sentence);
        if (*end) p = end + 1;  // '|'
        else p = end;  // '\0'
    } while (*p);

    orders[sen_num] = 0;

    return orders;
}

// 释放在堆区申请的内存
void freelist(char ***list) {
    for (char ***t = list; *t; t++)
        for (char **tt = *t; *tt; tt++)
            free(*tt);
    for (char ***t = list; *t; t++)
        free(*t);
    free(list);
}

void *emalloc(size_t n) {
    void *rv;
    if ((rv = malloc(n)) == NULL) fatal("out of memory", "", 1);
    return rv;
}

void *erealloc(void *p, size_t n) {
    void *rv;
    if ((rv = realloc(p, n)) == NULL)
        fatal("realloc() failed", "", 1);
    return rv;
}


/*
 * execute.c - code used by mini shell to execute commands
 */

#include    <stdio.h>
#include    <stdlib.h>
#include    <unistd.h>
#include    <string.h>
#include    <signal.h>
#include    <sys/wait.h>
#include    <sys/file.h>
#include    <glob.h>  // 利用glob()函数进行*的匹配

int redirect(char **cmd) {
    int ard = 0;  // append redirect, 追加重定向
    int rd = 0;  // 非追加重定向
    while (*cmd) {
        char *p = *cmd;
        int fd;
        if (ard || rd) {
            if (ard) fd = open(p, O_CREAT | O_APPEND | O_WRONLY, 0644);
            else fd = open(p, O_CREAT | O_TRUNC | O_WRONLY, 0644);
            dup2(fd, 1);
            close(fd);
            return 0;
        }
        while (*p && *p != '>') p++;
        if (*p) {  // 如果有重定向符 >
            *p = '\0', p++;
            if (*p == '>') ard = 1;  // 追加重定向
            else rd = 1;  // 不是追加重定向
            *cmd = NULL;
        }
        cmd++;
    }
    if (ard || rd) return -1;
    else return 0;
}

// 执行解析好的命令
int execute(char ***argv) {
    pid_t pid;
    int child_info = -1;
    int fds[2], fd = 0;

    while (*argv) {
        pipe(fds);
        if ((pid = fork()) == -1) perror("fork");
        else if (pid == 0) {
            close(fds[0]);
            dup2(fd, 0);

            if (*(argv + 1)) dup2(fds[1], 1);
            else {
                if (redirect(*argv) == -1) {  // 重定向后无内容
                    printf("zsh: parse error near `\\n'\n");
                    exit(1);
                }
            }

            signal(SIGINT, SIG_DFL);
            signal(SIGQUIT, SIG_DFL);

            glob_t buf;
            buf.gl_offs = buf.gl_pathc = 0;  // 初始化 offset 和 path_count

            // 计算offset,即第一个命令和后续命令选项的个数
            char **word = *argv;
            word++, buf.gl_offs++;  // 命令
            while (*word && **word == '-') word++, buf.gl_offs ++ ;  // 选项
            // GLOB_NOCHECK的作用是没有匹配的文件名时,使用模式串。用在 grep txt 类的命令中对 "txt" 的处理上
            if (*word) glob(*word++, GLOB_DOOFFS | GLOB_NOCHECK, NULL, &buf);  // 第一个参数
            while (*word) glob(*word++, GLOB_DOOFFS | GLOB_APPEND | GLOB_NOCHECK, NULL, &buf);  // 后续参数

            // 调用 glob() 会将 gl_pathv[] 的前offset个置为空,因此,要在调用 glob() 后对其赋值
            word = *argv, buf.gl_offs = 0;  // 重置
            buf.gl_pathv[buf.gl_offs++] = *word++;  // 命令
            while (*word && **word == '-') buf.gl_pathv[buf.gl_offs++] = *word++;  // 选项

            if (buf.gl_pathc) execvp((*argv)[0], &buf.gl_pathv[0]);  // 调用过glob()函数,即可能有*的情况
            else execvp((*argv)[0], *argv);  // 没调用过,则不会有*
            perror("cannot execute command");
            exit(1);
        } else {
            if (wait(&child_info) == -1) perror("wait");
            close(fds[1]);
            fd = fds[0];
            argv++;
        }
    }
    return child_info;
}