C系统编程:从零手搓一个shell

背景

这么久没更新就是在干这件事!!因为系统编程已经学的差不多了,所以想找几个项目练练手,之前就一直想写一个自己的shell!!现在终于有机会实现了。

首先说明一下我的操作系统:Arch linux

服务器操作系统:Ubantu 18.04.6

这两个系统上面我都尝试了我的代码,都可以正常运行!

设计思路

设计shell最难的地方有三个点:

  • 命令的分割
  • 命令的执行方式
  • 重定向和管道与命令执行的关系
    搞清楚这三点,一个shell可以说是手到擒来。

设计过程

在设计之前我先准备了一些用于调试的代码(因为我不会用调试软件,主要如下:

首先是字体颜色的代码:

c 复制代码
#ifndef _COLOR_H
#define _COLOR_H

#define NONE  "\e[0m"           //清除颜色,即之后的打印为正常输出,之前的不受影响
#define BLACK  "\e[0;30m"  //深黑
#define L_BLACK  "\e[1;30m" //亮黑,偏灰褐
#define RED   "\e[0;31m" //深红,暗红
#define L_RED  "\e[1;31m" //鲜红
#define GREEN  "\e[0;32m" //深绿,暗绿
#define L_GREEN   "\e[1;32m" //鲜绿
#define BROWN "\e[0;33m" //深黄,暗黄
#define YELLOW "\e[1;33m" //鲜黄
#define BLUE "\e[0;34m" //深蓝,暗蓝
#define L_BLUE "\e[1;34m" //亮蓝,偏白灰
#define PINK "\e[0;35m" //深粉,暗粉,偏暗紫
#define L_PINK "\e[1;35m" //亮粉,偏白灰
#define CYAN "\e[0;36m" //暗青色
#define L_CYAN "\e[1;36m" //鲜亮青色
#define GRAY "\e[0;37m" //灰色
#define WHITE "\e[1;37m" //白色,字体粗一点,比正常大,比bold小
#define BOLD "\e[1m" //白色,粗体
#define UNDERLINE "\e[4m" //下划线,白色,正常大小
#define BLINK "\e[5m" //闪烁,白色,正常大小
#define REVERSE "\e[7m" //反转,即字体背景为白色,字体为黑色
#define HIDE "\e[8m" //隐藏
#define CLEAR "\e[2J" //清除
#define CLRLINE "\r\e[K" //清除行
#endif

这样就不用我们在需要输出带有颜色的字体的时候去查询对应的颜色编号了。

例如:如果想要输出一个红色字体的字符:

c 复制代码
printf("RED"hello world ! \n"NONE");

我还设计了一个日志函数,用于在程序中记录一些函数的执行信息,这样有利于进行bug调试:

c 复制代码
#include "./head.h"
#include "./log_message.h"

// 第一个参数是日志等级,第二个参数是文件路径,第三个参数是记录类型(有补充和添加两种) 后面两个参数是用来写入内容的,和printf以及scanf的用法一样。
void log_event(int level, const char *filename, int log_type, const char *format, ...) {
    time_t now = time(NULL);
    char *level_str;
    FILE *fp;
    va_list args;

    switch(level) {
        case LOG_LEVEL_INFO: {
            level_str = "INFO";
            break;
        }
        case LOG_LEVEL_WARNING: {
            level_str = "WARNING";
            break;
        }
        case LOG_LEVEL_ERROR : {
            level_str = "ERROR";
            break;
        }
        default: {
            level_str = "UNKNOWN";
            break;
        }
    }
    
    fp = fopen(filename, "a");

    if(fp == NULL) {
        perror("[Log]Open file ERROR!");
        return ;
    }

    if(fp != NULL) {
        fprintf(fp, "%s [%s]: ", ctime(&now), level_str);
        va_start(args, format);
        vfprintf(fp, format, args);
        va_end(args);
        fprintf(fp, "\n");
        fclose(fp);
    }
    return ;
}

还有头文件哦:

c 复制代码
#ifndef _LOG_MESSAGE_H
#define _LOG_MESSAGE_H

#define LOG_LEVEL_INFO 0
#define LOG_LEVEL_WARNING 1
#define LOG_LEVEL_ERROR 2
#define LOG_LEVEL_UNKNOWN 3

#define LOGT_add 0
#define LOGT_new 1

void log_event(int level, const char *filename, int log_type, const char *format, ...);

#endif

接下来是我的主要头文件,所有调用的库以及DBG函数都存放在这个文件中:

c 复制代码
#ifndef _HEAD_H
#define _HEAD_H

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <semaphore.h>
#include <libgen.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>

//---head file from user

#include "color.h"

//------------------------------ DBG ------------------------------ 
#ifdef _D


/* 
 * DBG : aimed to printf the name and num (enough char, int or other) of the variable.
 * 
 */

#define DBG(fmt, args...) printf(fmt, ##args)

// 泛型选择器
#define _AUTO_DBG(arg) _Generic((arg),\
    int: printf(""#arg" = %d\n", (arg)),\
    long: printf(""#arg" = %ld\n", (arg)),\
    long long: printf(""#arg" = %lld\n", (arg)),\
    float: printf(""#arg" = %f\n", (arg)),\
    double: printf(""#arg" = %lf\n", (arg)),\
    long double: printf(""#arg" = %Lf\n", (arg)),\
    char: printf(""#arg" = %c\n", (arg)),\
    char*: printf(""#arg" = '%s'\n", (arg)),\
    default: printf(""#arg" = %p\n", (arg))\
)

#define _D_DBG(arg) printf("DBG:"#arg" = %d\n", arg);
#define _F_DBG(arg) printf("DBG:"#arg" = %f\n", arg);
#define _LF_BG(arg) printf("DBG:"#arg" = %lf\n", arg);
#define _LLD_NDBG(arg) printf("DBG:"#arg" = %lld\n", arg);
#define _LLF_DBG(arg) printf("DBG:"#arg" = %llf\n", arg);
#define _S_DBG(arg) printf("DBG:"#arg" = '%s'\n", arg);
#define _C_DBG(arg) printf("DBG:"#arg" = %c\n", arg);
#define _G_DBG(arg) printf("DBG:"#arg" = %g\n", arg);

#else

#define DBG(fmt, args...)
#define _AUTO_DBG(arg)
#define _D_DBG(arg)
#define _F_DBG(arg)
#define _LF_DBG(arg)
#define _LLD_DBG(arg)
#define _LLF_DBG(arg)
#define _S_DBG(arg)
#define _C_DBG(arg)
#define _G_DBG(arg)

#endif


#endif

当然,并不是所有的头文件和宏定义都能用的上,这是我学习过程中积累下来的一个头文件,写任何系统编程的程序都会用的到。

值得提及的是DBG这个宏,非常的好用,我只需要在编译时加上_D的参数就可以使得代码中所有的DBG代码全部有效,如果不使用_D,就不会执行,这样以来想要看代码中执行情况时就可以使用这个函数,想要看正常运行的时候就可以不加参数,非常方便!!!

我的所有辅助我进行编程的头文件就是这些了,接下来的两个文件就是我的shell程序的文件了。

因为代码量不大,我没有选择分开文件去设计我的程序,而是放在了一个文件里面,这样当我想要去修改头文件的时候也很方便!

先上.h文件:
myshell.h

c 复制代码
#ifndef _MYSHELL_H
#define _MYSHELL_H

#define MAX_NAME_LEN        128             // 用户名最大长度
#define MAX_HOST_NAME_LEN   128             // 主机名最大长度
#define MAX_DIRECTORY_LEN   128             // 目录最大长度

#define BUFF_LEN            1024            // 普通缓冲长度,用于程序中一些错误信息的存储
#define MAX_BUFF_LEN        64              // 单个命令参数缓冲最大长度,应当与MAX_CMD_LEN保持相同值
#define MAX_CMD_NUM         64              // 最大支持参数个数
#define MAX_CMD_LEN         64              // 单个命令参数最大长度
#define MAX_ENV_LEN         8192            // 维护的环境变量的最大长度

/* 内置命令标号 */
#define COMMENDF_EXIT       0               // exit
#define COMMENDF_CD         1               // cd

#define Ture 1
#define False 0
#define clear_flush_scanf() while(getchar() != '\n')

/** 命令行 **/
const char* COMMAND_EXIT = "exit";
const char* COMMAND_ECHO = "echo";
const char* COMMAND_CD = "cd";
const char* COMMAND_IN = "<";
const char* COMMAND_OUT = ">";
const char* COMMAND_PIPE = "|";

char UserName[MAX_NAME_LEN];
char HostName[MAX_HOST_NAME_LEN];
char CurWorDir[MAX_DIRECTORY_LEN];                    // 记录当前工作路径的全路径
char *BaseWorDir;                                     // 记录当前工作路径的最后一层路径
char CmdProSym;

// 内置状态码
enum {
        RESULT_NORMAL,
        ERROR_FORK,
        ERROR_COMMAND,
        ERROR_WRONG_PARAMETER,
        ERROR_MISS_PARAMETER,
        ERROR_TOO_MANY_PARAMETER,
        ERROR_CD,
        ERROR_SYSTEM,
        ERROR_EXIT,

        /* 重定向的错误信息 */
        ERROR_MANY_IN,
        ERROR_MANY_OUT,
        ERROR_FILE_NOT_EXIST,

        /* 管道的错误信息 */
        ERROR_PIPE,
        ERROR_PIPE_MISS_PARAMETER
};

/* 参数数量以及参数数组 */
int argc;
char argv[MAX_CMD_NUM][MAX_CMD_LEN];

/* ---------------------- init ---------------------- */
int Get_Username(); // 获取当前用户名
int Get_hostname(); // 获取当前主机名
int Get_WorDir(); // 获取当前所在目录
inline void Init_ShellState(); // 执行启动之后的初始化等操作

/* 解析命令函数 */
void Get_Commands();
int isPartation(char *buff);
int __get_cmds(char *buff);

int isCommandExist(const char *command);
bool isWrapByCitation(const char *buff);
bool isVariable(const char *buff);
bool isWrapByBigPar(const char *buff);
char *getVarInPar(const char *buff);
/* --------------------- call ---------------------- */
int callExit();
int callCd();

int __callCommands_Pipe_(int l, int r);
int __callCommands_Redi_(int l, int r);
int callCommands();


/* ---------------------- handle --------------------- */

bool Cd_result_handle(int result);               // cd命令的结果处理函数
bool Other_Commands_result_handle(int result);

#endif

头文件不多做解释,毕竟都是函数声明嘛~~~

接下来说一下我设计程序的主要思路:

因为shell分为内置命令和外置命令,因此我一开始想要将所有的内置命令写出来,而外置命令使用exec函数族去实现,但是我发现我真的想得很简单。

因为有一部分内置命令(如echo,pwd)也有输出的功能,如果直接分成模块来写内置命令的话,在重定向和命令解析的函数中就会很难做...

因此我选择先写cd命令和exit指令,以这两个指令为优先级最高对待,如果命令中存在exit,则直接退出程序,对于cd命令,我们判断后面第二个参数,这样一来,两个内置命令就完成了。

对于剩下的命令,我们统一分成三种情况,存在管道的和存在重定向的以及什么也不存在的。为什么要这样分呢?其实很简单,

每次生出一个新的进程去执行下一段命令,如果是带有管道的命令,则提前通过dup以及dup2等函数将标准输入输出转换一下,使得管道后的程序使用读端,管道前的程序使用写端,执行完程序之后还需要还原标准输入输出文件描述符。整个命令的解析是一种递归的方式。

下面上代码:

c 复制代码
#include "./common/head.h"
#include "./common/log_message.h"
#include "./myshell.h"

int main() {
    int result;
    pid_t pid;
    int status;
    Init_ShellState();

    if ((pid = fork()) == -1) {
        perror("[fork]ERROR_FORK");
        exit(ERROR_FORK);
    }

    if (pid != 0) {
        int status;
        wait(&status);
        return 0;
    } else if (pid == 0) {
        while (true) {
            printf(RED "%s" NONE "@" BLUE "%s " YELLOW "%s " NONE "%c ", UserName, HostName, BaseWorDir, CmdProSym);
            Get_Commands();
            if (argc != 0) {
                for (int i = 0; i < argc; ++i) {
                    if (strcmp(argv[i], "exit") == 0) {
                        result = callExit();
                        if (ERROR_EXIT == result) {
                            log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "ERROR EXIT!\n");
                            exit(1);
                        }
                    }
                }  

                // run "cd"
                if (strcmp(argv[0], COMMAND_CD) == 0) {
                    result = callCd();
                    if (Cd_result_handle(result)) exit(ERROR_SYSTEM);
                } else { 
                    /* run "other commands" */
                    result = callCommands();
                    if (Other_Commands_result_handle(result)) exit(ERROR_SYSTEM);
                }
            }
        }
    }
}

/* 获取当前工作目录, 用户名, 主机名*/
inline void Init_ShellState() {
    Get_WorDir();
    Get_Username();
    Get_hostname();
    if (strcmp("root", UserName) == 0) CmdProSym = '$';
    else CmdProSym = '%';
    return ;
}

int Get_Username() {
    char *temp = getenv("USER");
    if (temp == NULL) {
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "Feild to get environment of \"USER\"");
        return ERROR_SYSTEM;
    }
    strcpy(UserName, temp);
    log_event(LOG_LEVEL_INFO, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "UserName = \"USER\" is %s\n", UserName);
    return RESULT_NORMAL;
}

int Get_hostname() {
    if (gethostname(HostName, MAX_HOST_NAME_LEN) == -1) {
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "Not found hostname!\n");
        return ERROR_SYSTEM;
    }
    log_event(LOG_LEVEL_INFO, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "Find the hostname : %s\n", HostName);
    return RESULT_NORMAL;
}

int Get_WorDir() {
    char *result = getcwd(CurWorDir, MAX_DIRECTORY_LEN);
    if (result == NULL) {
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{Get_WorDir} : func > getcwd() error!\n");
        return ERROR_SYSTEM;
    }
    log_event(LOG_LEVEL_INFO, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "Path from (getcwd) = {%s}", CurWorDir);
    BaseWorDir = basename(CurWorDir);
    if (strcmp(BaseWorDir, UserName) == 0)  BaseWorDir = "~";
    return RESULT_NORMAL;
}

// 得到下一个由两个空格包裹的参数
// 并且存储到buff中
// 如果遇到换行符说明后面没有参数了,返回1
// 如果没有遇到换行符说明后面有参数,返回0
int __get_cmds(char *buff) {
    int buffsize = 0;
    char temp;
    memset(buff, 0, sizeof(buff));
    while((temp = getchar()) == ' ' && temp != '\n');
    //DBG(RED "First char = %c\n" NONE, temp);
    if(temp == '\n') return 1;
    do {
        buff[buffsize++] = temp;
        //DBG(YELLOW "buff[%d] = %c\n" NONE, buffsize - 1, temp);
    } while((temp = getchar()) != ' ' && temp != '\n');
    if(temp == '\n') return 1;
    else return 0;
}

void Get_Commands(){
    char buff[MAX_BUFF_LEN] = {0};

    // init all arguments
    argc = 0;
    memset(argv, 0, sizeof(argv));

    while(!__get_cmds(buff)) {
        DBG(YELLOW "buff = {%s}\n" NONE, buff);
        strcpy(argv[argc++], buff);
    }
    if (strcmp("", buff) != 0) strcpy(argv[argc++], buff);
    DBG(YELLOW "last buff = {%s}\n" NONE, argv[argc - 1]);
    return ;
}

int callExit() {
    pid_t cpid = getpid();
    if (kill(cpid, SIGTERM) == -1) 
    return ERROR_EXIT;
    else return RESULT_NORMAL;
}

int callCd() {
    int result = RESULT_NORMAL;
    if (argc == 1) {
        const char* home = getenv("HOME");
        //DBG("{callCd} : home : |%s|\n", home);
        if (home != nullptr) {
            int ret = chdir(home);
            //DBG("{callCd} : %d\n", ret);
            if (ret) {
                result = ERROR_WRONG_PARAMETER;
                log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{callCd} : \"cd\" not found directory of user.\n");
            }
        } else {
            result = ERROR_MISS_PARAMETER;
        }
    } else if (argc > 2) {
        result = ERROR_TOO_MANY_PARAMETER;
    } else {
        int ret = chdir(argv[1]);
        //DBG("{callCd} : %d\n", ret);
        if (ret) result = ERROR_WRONG_PARAMETER;
    }
    return result;
}

bool Cd_result_handle(int result) {
    switch (result) {
        case ERROR_MISS_PARAMETER:
        fprintf(stderr, "" RED "Error: Miss parameter while using command \"%s\" \n" NONE "", COMMAND_CD);
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{Cd_result_handle} : Miss parameter while using command \"%s\".\n", COMMAND_CD);
        return false;

        case ERROR_WRONG_PARAMETER:
        fprintf(stderr, "" RED "Error: No such path \"%s\".\n" NONE "", argv[1]);
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{Cd_result_handle} : \"%s\" Path not found.\n", COMMAND_CD);
        return false;

        case ERROR_TOO_MANY_PARAMETER:
        fprintf(stderr, "" RED "Error: Too many parameters while using command \"%s\".\n" NONE "", COMMAND_CD);
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{Cd_result_handle} : \"%s\" Too many parameters while using command\n", COMMAND_CD);
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_add, "Usage : cd ./path\n");
        return false;

        case RESULT_NORMAL:
        result = Get_WorDir();
        if (ERROR_SYSTEM == result) {
            log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{Cd_result_handle} : Lost path after cd.\n");
            return true;
        } else {
            log_event(LOG_LEVEL_INFO, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{Cd_result_handle} : cd OK.\n");
            return false;
        }

        default:
        log_event(LOG_LEVEL_UNKNOWN, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "Unknow what happend at func : {Cd_result_handle}\n");
        return true;
    }
}

// 执行可能存在管道的命令
int __callCommands_Pipe_(int l, int r) {
    if (l >= r) return RESULT_NORMAL; // 没有可以执行的命令了
    int pipeIdx = -1;
    for (int i = l; i < r; ++i) {
        if (strcmp(argv[i], COMMAND_PIPE) == 0) {
            pipeIdx = i;
            break;
        }
    }

    // 没有管道,执行可能存在重定向的命令
    if (pipeIdx == -1) {
        return __callCommands_Redi_(l, r);
    } else if (pipeIdx + 1 == argc) {
        // 管道在最末尾
        return ERROR_PIPE_MISS_PARAMETER;
    }

    // 正式执行命令
    int fds[2];
    if (pipe(fds) == -1) {
        return ERROR_PIPE;
    }
    int result = RESULT_NORMAL;
    pid_t pid = vfork();
    if (pid == -1) {
        result = ERROR_FORK;
    } else if (pid == 0) {
        close(fds[0]);
        dup2(fds[1], STDOUT_FILENO); // 将这个命令执行的输出输入到写端
        close(fds[1]);
        result = __callCommands_Redi_(l, pipeIdx);
        exit(result);
    } else {
        // 父进程等待子进程的退出状态
        int status;
        waitpid(pid, &status, 0);
        int exitCode = WEXITSTATUS(status);

        if (exitCode != RESULT_NORMAL) {
            char info[BUFF_LEN] = {0};
            char line[BUFF_LEN] = {0};
            close(fds[1]);
            dup2(fds[0], STDIN_FILENO);
            close(fds[0]);
            while (fgets(line, BUFF_LEN, stdin) != NULL) {
                strcat(info, line);
            }
            printf("%s", info); // 打印错误
            result = exitCode;
        } else if (pipeIdx + 1 < r) {
            close(fds[1]);
            dup2(fds[0], STDIN_FILENO);
            close(fds[0]);
            result = __callCommands_Pipe_(pipeIdx + 1, r);
        }
    } 
    return result;
}

// 不存在管道,调用可能存在重定向的函数
int __callCommands_Redi_(int l, int r) {
    // 判断命令是否存在
    if (!isCommandExist(argv[l])) {
        return ERROR_COMMAND;
    }
    int inNum = 0, outNum = 0;
    // 存储重定向的文件路径
    char *inFile = NULL, *outFile = NULL;
    int endIdx = r; // 第一个重定向符号的位置

    // 寻找重定向符号并且记录
    for (int i = l; i < r; ++i) {
        if (strcmp(argv[i], COMMAND_IN) == 0) {
            ++inNum;
            if (i + 1 < r) {
                inFile = argv[i + 1];
            } else return ERROR_MISS_PARAMETER;
            if (endIdx == r) endIdx = i;
        } else if (strcmp(argv[i], COMMAND_OUT) == 0) {
            ++outNum;
            if (i + 1 < r) {
                outFile = argv[i + 1];
            } else return ERROR_MISS_PARAMETER;
            if (endIdx == r) endIdx = i;
        }
    }

    // 非法情况判断
    if (inNum > 1) { // 输入重定向符超过一个
                return ERROR_MANY_IN;
        } else if (outNum > 1) { // 输出重定向符超过一个
                return ERROR_MANY_OUT;
        }

    // 判断文件是否存在
        if (inNum == 1) {
                FILE* fp = fopen(inFile, "r");
                if (fp == NULL) {
            return ERROR_FILE_NOT_EXIST;
        }
        fclose(fp);
        }
    
    int result = RESULT_NORMAL;
    pid_t pid = vfork();
    if (pid == -1) {
        return ERROR_FORK;
    } else if (pid == 0) {
        // 这里的两个标准IO已经在外层被重定向过了
        if (inNum == 1) freopen(inFile, "r", stdin);
        if (outNum == 1) freopen(outFile, "w", stdout); 

        char *tcommand[BUFF_LEN];
        for (int i = l; i < endIdx; ++i) {
            tcommand[i] = argv[i];
        }
        tcommand[endIdx] = NULL;
        
        // echo 内置命令
        if (strcmp(tcommand[l], COMMAND_ECHO) == 0) {
            // 被花括号包裹的情况
            if(isWrapByCitation(tcommand[l + 1])) {
                int len = strlen(tcommand[l + 1]);
                for (int i = 1; i < len - 1; ++i) {
                    fprintf(stdout, "%c", tcommand[l + 1][i]);
                }
                fprintf(stdout, "\n");
            } else if (isVariable(tcommand[l + 1])) {
                // 存在变量符号的情况
                char *temp;
                if (isWrapByBigPar(tcommand[l + 1])) {
                    // 变量名被花括号包裹
                    char *t = getVarInPar(tcommand[l + 1]);
                    temp = getenv(t);
                    free(t); // 及时释放内存
                } else {
                    // 没有被包裹
                    temp = getenv(tcommand[l + 1] + 1);
                }
                if (temp == NULL) {
                    fprintf(stdout, "\n");
                } else {
                    // 什么也不是的情况,直接输出原话
                    fprintf(stdout, "%s\n", temp);
                }
            } else {
                fprintf(stdout, "%s\n", tcommand[l + 1]);
            }
        } else execvp(tcommand[l], tcommand + l); // 执行命令并输出到stdout
        // 如果执行到这里说明execvp执行失败了,返回错误
        exit(errno);
    } else {
        int status;
        waitpid(pid, &status, 0);
        int err = WEXITSTATUS(status);
        if (err) {
            printf(RED"Error : %s\n", strerror(err));
        }
    }
    return result;
}

char *getVarInPar(const char *buff) {
    int len = strlen(buff);
    char *temp = (char *)malloc(sizeof(char) * (len - 3));
    memset(temp, 0, sizeof(temp));
    for (int i = 2; i <= len - 2; ++i) {
        temp[i - 2] = buff[i];
    }
    return temp;
}

bool isWrapByBigPar(const char *buff) {
    int len = strlen(buff);
    if (len == 0) return false;
    if (buff[1] == '{' && buff[len - 1] == '}') return true;
    else return false;
}

bool isVariable(const char *buff) {
    if (buff[0] == '$') return true;
    else return false;
}

bool isWrapByCitation(const char *buff) {
    int len = strlen(buff);
    if (len == 0) return false;
    if (buff[0] == '\"' && buff[len - 1] == '\"') return true;
    else return false;
}

int isCommandExist(const char *command) {
    if (command == NULL || strlen(command) == 0) return false;
    int result = true;
    int fds[2];
    if (pipe(fds) == -1) {
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{isCommandExist} : pipe(fds) == -1");
        result = false;
    } else {
        int inFd = dup(STDIN_FILENO);
        int outFd = dup(STDOUT_FILENO);

        pid_t pid = vfork();
        if (pid == -1) {
            log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{isCommandExist} : vfork fild!");
            result = false;
        } else if (pid == 0) {
            char tcommand[BUFF_LEN];
            close(fds[0]);
            dup2(fds[1], STDOUT_FILENO);
            close(fds[1]);
            sprintf(tcommand, "command -v %s", command);
            system(tcommand); // 执行命令检查
            exit(1);
        } else {
            waitpid(pid, NULL, 0);
            close(fds[1]);
            dup2(fds[0], STDIN_FILENO);
            close(fds[0]);
            if (getchar() == EOF) {
                log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "{isCommandExist} : command -v Not found!\n");
                result = false;
            }
            dup2(inFd, STDIN_FILENO);
            dup2(outFd, STDOUT_FILENO);
        }
    }
    return result;
}

// 外部函数调用命令
int callCommands() {
    pid_t pid = fork();
    if (pid == -1) {
        return ERROR_FORK;
    } else if (pid == 0) {
        // 为了使子进程接管标准输入输出,需要重定向标准输入输出文件符号
        // 因此需要记录下来
        int inFd = dup(STDIN_FILENO);
        int outFd = dup(STDOUT_FILENO);
        int result = __callCommands_Pipe_(0, argc);
        dup2(inFd, STDIN_FILENO);
        dup2(outFd, STDOUT_FILENO);
        exit(result);
    } else {
        // 父进程等待子进程执行命令
        int status;
        waitpid(pid, &status, 0);
        return WEXITSTATUS(status);
    }
    return 0;
}

bool Other_Commands_result_handle(int result) {
    switch (result) {
        case ERROR_FORK:
        fprintf(stderr, "\e[31;1mError: Fork error.\n\e[0m");
        log_event(LOG_LEVEL_ERROR, "/home/royi/1.OS_programing/9.myshell/log.txt", LOGT_new, "result from {callCommands} : vfork() faild!\n");
        return true; // exit
        
        case ERROR_COMMAND:
        fprintf(stderr, "\e[31;1mError: Command not exist in myshell.\n\e[0m");
        return false;

        case ERROR_MANY_IN:
        fprintf(stderr, "\e[31;1mError: Too many redirection symbol \"%s\".\n\e[0m", COMMAND_IN);
        return false;
        
        case ERROR_MANY_OUT:
        fprintf(stderr, "\e[31;1mError: Too many redirection symbol \"%s\".\n\e[0m", COMMAND_OUT);
        return false;
        
        case ERROR_FILE_NOT_EXIST:
        fprintf(stderr, "\e[31;1mError: Input redirection file not exist.\n\e[0m");
        return false;
        
        case ERROR_MISS_PARAMETER:
        fprintf(stderr, "\e[31;1mError: Miss redirect file parameters.\n\e[0m");
        return false;
        
        case ERROR_PIPE:
        fprintf(stderr, "\e[31;1mError: Open pipe error.\n\e[0m");
        return false;
        
        case ERROR_PIPE_MISS_PARAMETER:
        fprintf(stderr, "\e[31;1mError: Miss pipe parameters.\n\e[0m");
        return false;
    }
    return 0;
}

下面是一些执行结果:

相关推荐
全栈开发圈几秒前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫
面试鸭5 分钟前
离谱!买个人信息买到网安公司头上???
java·开发语言·职场和发展
小白学大数据5 分钟前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
Python大数据分析@9 分钟前
python操作CSV和excel,如何来做?
开发语言·python·excel
上海_彭彭34 分钟前
【提效工具开发】Python功能模块执行和 SQL 执行 需求整理
开发语言·python·sql·测试工具·element
3345543242 分钟前
element动态表头合并表格
开发语言·javascript·ecmascript
沈询-阿里1 小时前
java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl
java·开发语言
残月只会敲键盘1 小时前
面相小白的php反序列化漏洞原理剖析
开发语言·php
ac-er88881 小时前
PHP弱类型安全问题
开发语言·安全·php
ac-er88881 小时前
PHP网络爬虫常见的反爬策略
开发语言·爬虫·php