Linux內核實驗(二):shell命令解釋系統

一、引言

Shell命令解釋程序中元字符的處理是shell中一個強大的功能,利用對各種字符的不同解釋可以充分挖掘出內核的各種強大的潛能。例如利用“&”符號啓動併發的後臺進程,利用“<”、“>”符號啓動I/O重定向,利用“|”啓動管道讀寫等。同樣也可以實現其他一些元字符的功能,如“*”通配符,“;”連接符等。

二、實驗內容

問題A
實現一個能處理前後臺運行命令的shell
問題B
實現一個帶有管道功能的shell
問題C
實現一個能處理I/O重定向的shell
問題D
實現一個能在一行上處理多條命令的shell

三、實驗代碼

/*****************************************
*
* Shell命令解釋系統
*
* Copyright: (C) 2018.4.14 by shaomingshan
*
* Compile: gcc -I/usr/include -lreadline -g -o main main.c
*
* Execute: ./main
*          ps aux | sort | uniq > aaa
*          cat aaa
*
*****************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <readline/readline.h>
#include <sys/stat.h>
#include <wait.h>

#define LS 1024
#define DELIM "<>;|"

int lineeof(char * str) {
    /* 判斷命令是否完成輸入 */
    int len = strlen(str);
    while (str[len-1] == 32) len--;
    if (str[len-1] == 92) {
        str[len-1] = '\0';
        return 0;
    }
    return 1;
}

void exec(char * cmd) {
    /* 執行命令 */
    char *eargv[16];
    int i = 0;
    eargv[i] = strtok(cmd, " ");
    while (eargv[i++] != NULL) {
        eargv[i] = strtok(NULL, " ");
    }
    eargv[i] = 0;
    execvp(eargv[0], eargv);
}

char * trim(char *str) {
    /* 去前後空白 */
    int l = 0;
    int r = strlen(str)-1;
    while (str[l] == ' ') l++;
    while (str[r] == ' ') r--;
    str[r+1] = '\0';
    return &str[l];
}

void next_cmd(char *str, int *i) {
    /* 找到下一個分命令開始地方 */
    for (; *i < strlen(str); ++*i) {
        if (str[*i+1]==';') break;
    }
}

int main() {

    char  *line, *temp, *raw, *flow;
    char  *filein, *fileout, *cmd;
    char  nil;
    int   i, argc, status, in_arg, jobs=0;
    int   is_bkg, is_pip, k=0;
    pid_t pid[3], bpid[3];
    char  *argv[LS/8];
    char  *fifo[3] = {"fifo0", "fifo1", "fifo2"};

    raw     = (char *) malloc(LS);
    line    = (char *) malloc(LS);
    temp    = (char *) malloc(LS);
    flow    = (char *) malloc(LS/8);
    cmd     = NULL;                     // 存放命令主體
    filein  = NULL;                     // 重定向輸入
    fileout = NULL;                     // 重定向輸出

    for (i=0;i<3;++i) {
        unlink(fifo[i]);
        mkfifo(fifo[i], 0666);
    }

    while (1) {

        raw = trim(readline("$> "));

        if (!strlen(raw)) continue;     // 空行
        if (!strcmp(raw,"exit")) break; // exit命令

        while (!lineeof(raw)) {
            // 命令是否結束,"\"符號
            raw = strcat(raw, readline(" >   "));
        }

        memset(line, 0, LS);
        memcpy(line, raw, strlen(raw));
        argc = 0;
        is_pip = 0;
        in_arg = -1;

        temp = strtok(line, DELIM);
        while (temp != NULL) {
            in_arg += strlen(temp)+1;

            flow[argc] = raw[in_arg];
            argv[argc++] = trim(temp);
            temp = strtok(NULL, DELIM);
        }
        flow[argc-1] = ';';
        flow[argc] = '\0';

        for (i = 0; i < strlen(flow); ++i) {
            switch (flow[i]) {

                case ';':
                    // 判斷是否後臺
                    nil = argv[i][strlen(argv[i])-1];
                    is_bkg = nil=='&'? 1 : 0;

                case '|':

                    cmd = cmd? cmd : argv[i];       // 存儲命令

                    // exec無法處理cd命令,只能單獨處理
                    if (strstr(cmd, "cd")==cmd) {
                        // 字符串首爲cd
                        if (strlen(cmd)==2) {
                            // HOME
                            chdir("/home/fio");
                        } else if (cmd[2]==' ') {
                            // 且有參數,命令是trim過的
                            char * dir = &cmd[3];
                            chdir(dir);
                        }
                    } else {
                        pid[k] = fork();
                        if (pid[k] == 0) {
                            // input 管道輸入優先於重定向來源
                            if (is_pip) {
                                stdin = freopen(fifo[k], "r", stdin);
                            } else if (filein) {
                                stdin = freopen(filein, "r", stdin);
                            }

                            // output 重定向文件優先於管道
                            k = (k+1)%3;    // 輪轉一下
                            if (fileout) {
                                stdout = freopen(fileout, "w", stdout);
                                if (flow[i]=='|') {
                                    // ls > b | wc
                                    // 需要打開管道再關閉
                                    int fd = open(fifo[k], O_WRONLY);
                                    close(fd);
                                }
                            } else if (flow[i]=='|') {
                                stdout = freopen(fifo[k], "w", stdout);
                            }

                            exec(cmd);      // 執行命令

                        } else {
                            if (is_pip) {
                                // 回收,如果當前是管道則不回收,交給下一個子進程執行再回收
                                waitpid(pid[(k+2)%3], &status, 0);
                                if (status != 0) {
                                    printf("error: exit with code %d[%d]\n", status, pid[(k+2)%3]);
                                    // 跳到下一個分命令
                                    next_cmd(flow, &i);
                                }
                            }
                            if (!is_bkg && (flow[i]!='|'||fileout)) {
                                // 回收前一個管道命令子進程
                                waitpid(pid[k], &status, 0);
                                if (status != 0) {
                                    printf("Error: exit with code %d[%d]\n", status, pid[k]);
                                    next_cmd(flow, &i);
                                }
                            } else if (is_bkg) {
                                bpid[jobs++] = pid[k];      // 存放後臺命令,未完全實現
                            }

                            is_pip = flow[i] == '|'? 1:0;
                            k = (k+1)%3;
                            cmd = NULL;
                            filein = NULL;
                            fileout = NULL;
                        }
                    }
                    break;
                case '>':
                    cmd = cmd ? cmd : argv[i];
                    fileout = argv[i+1];
                    break;
                case '<':
                    if (!cmd) cmd = argv[i];
                    filein = argv[i+1];
                    break;
                default:
                    printf("Syntax Error\n");
                    exit(-1);
            }
        }
    }
}

四、運行結果

這裏寫圖片描述
這裏寫圖片描述

如有錯誤請指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章