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

背景

这么久没更新就是在干这件事!!因为系统编程已经学的差不多了,所以想找几个项目练练手,之前就一直想写一个自己的shell!!现在终于有机会实现了。
首先说明一下我的操作系统:Arch linux
服务器操作系统:Ubantu 18.04.6
这两个系统上面我都尝试了我的代码,都可以正常运行!

设计思路

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

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

设计过程

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

#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

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

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

printf("RED"hello world ! \n"NONE");

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

#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 ;
}

还有头文件哦:

#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函数都存放在这个文件中:

#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

#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等函数将标准输入输出转换一下,使得管道后的程序使用读端,管道前的程序使用写端,执行完程序之后还需要还原标准输入输出文件描述符。整个命令的解析是一种递归的方式。

下面上代码:

#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;
}

下面是一些执行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/570933.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HFSS端口介绍2---波端口

前面我们讨论了Lumped Port设定相关的内容,这节我们继续讨论Wave Port(波端口)使用相关的问题。 波端口使用范围 封闭结构:如波导、同轴电缆等 包含多个传播模式的模型 端口平面在求解区域外的模型 模型中包含均匀的波导或者传输线结构 波端口的大小 对于封闭的传输线结构:边…

【C++】vector常用函数总结及其模拟实现

目录 一、vector简介 二、vector的构造 三、vector的大小和容量 四、vector的访问 五、vector的插入 六、vector的删除 简单模拟实现 一、vector简介 vector容器&#xff0c;直译为向量&#xff0c;实践中我们可以称呼它为变长数组。 使用时需添加头文件#include<v…

【御控工业物联网】JAVA JSON结构转换、JSON结构重构、JSON结构互换(5):对象To对象——转换映射方式

御控官网&#xff1a;https://www.yu-con.com/ 文章目录 御控官网&#xff1a;[https://www.yu-con.com/](https://www.yu-con.com/)一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON对象》四、代码实现五、在线转换工具六、技术资料 一、JSON结构…

MySQL索引为什么选择B+树,而不是二叉树、红黑树、B树?

12.1.为什么没有选择二叉树? 二叉树是一种二分查找树,有很好的查找性能,相当于二分查找。 二叉树的非叶子节值大于左边子节点、小于右边子节点。 原因: 但是当N比较大的时候,树的深度比较高。数据查询的时间主要依赖于磁盘IO的次数,二叉树深度越大,查找的次数越多,性能…

openstack-镜像封装 7

再克隆两台主机并且安装图形化组件和虚拟化组件 进入图形化界面并安装一个虚拟化管理器 根下创建一个目录&#xff0c;虚拟化管理器新添加一个路径 创建虚拟化 配置虚拟化主机 设置虚拟化主机配置 安装所需软件 清理创建云主机时安装的组件 主机安装虚拟化工具 清理虚拟化缓存 …

Mysql全局优化总结

Mysql全局优化总结 从上图可以看出SQL及索引的优化效果是最好的&#xff0c;而且成本最低&#xff0c;所以工作中我们要在这块花更多时间 服务端系统参数 官方文档&#xff1a;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_connections…

x汽车登陆网站登陆rsa加密逆向

声明&#xff1a; 本文章内容仅供学习交流&#xff0c;不用于其他其他任何目的&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c; 各位看官好哇&#xff0c;今天给大家带来一篇web自动化逆向的文章&#xff0c;如下图当前我…

CMplot rMVP | 全基因组曼哈顿图和QQ图轻松可视化!

文章目录 1.CMplot1.1 CMplot介绍1.2 CMplot-DEMO1.3 CMplot参数 2.rMVP2.1 rMVP介绍2.2 rMVP-DEMO2.3 rMVP参数 1.CMplot 1.1 CMplot介绍 CMplot&#xff1a;https://github.com/YinLiLin/CMplot 这是一个做全基因组对SNP可视化神器了&#xff0c;尹立林教授写的R包。主打两…

Uptime Kuma 使用指南:一款简单易用的站点监控工具

我平时的工作会涉及到监控&#xff0c;而站点是一个很重要的监控项。项目上线后&#xff0c;我们通常会将站点监控配置到云平台上&#xff0c;以检测各站点的连通性。但随着项目不断增多&#xff0c;云平台上的配额就有点捉急了。针对这个情况&#xff0c;我们可以试试这个开源…

GPT-SoVITS声音克隆训练和推理(新手教程,附整合包)

环境: Win10 专业版 GPT-SoVITS-0421 整合包 问题描述: GPT-SoVITS声音克隆如何训练和推理教程 解决方案: Zero-shot TTS: Input a 5-second vocal sample and experience instant text-to-speech conversion.零样本 TTS:输入 5 秒的人声样本并体验即时文本到语音转换…

CentOS-7安装Mysql并允许其他主机登录

一、通用设置&#xff08;分别在4台虚拟机设置&#xff09; 1、配置主机名 hostnamectl set-hostname --static 主机名2、修改hosts文件 vim /etc/hosts 输入&#xff1a; 192.168.15.129 master 192.168.15.133 node1 192.168.15.134 node2 192.168.15.136 node33、 保持服…

设计模式-00 设计模式简介之几大原则

设计模式-00 设计模式简介之几大原则 本专栏主要分析自己学习设计模式相关的浅解&#xff0c;并运用modern cpp 来是实现&#xff0c;描述相关设计模式。 通过编写代码&#xff0c;深入理解设计模式精髓&#xff0c;并且很好的帮助自己掌握设计模式&#xff0c;顺便巩固自己的c…

【架构方法论(一)】架构的定义与架构要解决的问题

文章目录 一. 架构定义与架构的作用1. 系统与子系统2. 模块与组件3. 框架与架构4. 重新定义架构&#xff1a;4R 架构 二、架构设计的真正目的-别掉入架构设计的误区1. 是为了解决软件复杂度2. 简单的复杂度分析案例 三. 案例思考 本文关键字 架构定义 架构与系统的关系从业务逻…

【亲测有用】idea2024.1中前进后退按钮图标添加

idea更新后&#xff0c;前进后退按钮消失了&#xff0c;现在说下怎么设置 具体操作如下&#xff1a; 1、选择 File / Settings(windows版)&#xff0c;或者Preferences(mac版) 2、打开 Appearance & Behavior 并选择 Menus and Toolbars 3、选择右侧的 “Main toolbar lef…

第四百七十七回

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性 3. 示例代码4. 内容总结 我们在上一章回中介绍了"Get包简介"相关的内容&#xff0c;本章回中将介绍GetMaterialApp组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经…

C++:模板(初级)

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《C&#xff1a;模板&#xff08;初级&#xff09;》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0c;还请各位点点赞…

Docker 网络与资源控制

一 Docker 网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根 据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默 认网关。因为在同…

C++从入门到出门

C 概述 c 融合了3中不同的编程方式&#xff1a; C语言代表的过程性语言C 在C语言基础上添加的类代表的面向对象语言C 模板支持的泛型编程 1、在c语言中头文件使用扩展名.h,将其作为一种通过名称标识文件类型的简单方式。但是c得用法改变了&#xff0c;c头文件没有扩展名。但是…

Linux gcc day7

动态链接和静态链接 形成的可执行的程序小&#xff1a;节省资源--内存&#xff0c;磁盘 无法c静态库链接的方法 原因是我们没有安装静态c库&#xff08;.a&#xff09; 所以要安装 sudo yum install -y glibc-static gcc static静态编译选项提示错误&#xff1a;/usr/lib/ld:ca…
最新文章