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

news2024/11/18 0:31:38

背景

这么久没更新就是在干这件事!!因为系统编程已经学的差不多了,所以想找几个项目练练手,之前就一直想写一个自己的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.coloradmin.cn/o/1621102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ - STL详解(七)— stack和queue的介绍及使用

目录 一. stack 1.1 stack的介绍 1.2 stack的定义 1.3 stack的使用 ​编辑 二. queue 2.1 queue的介绍 2.2 queue的定义 2.3 queue的使用 一. stack 1.1 stack的介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除…

redis底层数据结构之ziplist

目录 一、概述二、ziplist结构三、Entry结构四、为什么ZipList特别省内存五、ziplist的缺点 redis底层数据结构已完结&#x1f44f;&#x1f44f;&#x1f44f;&#xff1a; ☑️redis底层数据结构之SDS☑️redis底层数据结构之ziplist☑️redis底层数据结构之quicklist☑️red…

ETL工具-nifi干货系列 第十六讲 nifi Process Group实战教程,一文轻松搞定

1、目前nifi系列已经更新了10多篇教程了&#xff0c;跟着教程走的同学应该已经对nifi有了初步的解&#xff0c;但是我相信同学们应该有一个疑问&#xff1a;nifi设计好的数据流列表在哪里&#xff1f;如何同时运行多个数据流&#xff1f;如启停单个数据流&#xff1f; 带着这些…

第二期书生浦语大模型训练营第四次笔记

大模型微调技术 大模型微调是一种通过在预训练模型的基础上&#xff0c;有针对性地微调部分参数以适应特定任务需求的方法。 微调预训练模型的方法 微调所有层&#xff1a;将预训练模型的所有层都参与微调&#xff0c;以适应新的任务。 微调顶层&#xff1a;只微调预训练模型…

Pandas数据分析小技巧

Pandas数据分析小技巧&#xff1a;提升数据处理效率与准确性的秘诀 Pandas是一个强大的Python数据分析库&#xff0c;它提供了快速、灵活且富有表现力的数据结构&#xff0c;使得数据清洗、转换、分析等操作变得简单而高效。本文将介绍一些Pandas数据分析的小技巧&#xff0c;…

年如何在不丢失数据的情况下解锁锁定的 Android 手机?

当您忘记密码、PIN 码或图案并且想要解锁 Android 手机时&#xff0c;您可能会丢失 Android 手机上的数据。但您无需再担心&#xff0c;因为在这里&#xff0c;我们想出了几种解锁锁定的 Android 手机而不丢失数据的方法。 方法 1. 使用 Android Unlock 解锁锁定的 Android 且不…

【上海大学计算机组成原理实验报告】四、指令系统实验

一、实验目的 了解指令结构、PC寄存器的功能和指令系统的基本工作原理。 学习设计指令的方法。 二、实验原理 根据实验指导书的相关内容&#xff0c;对于部分使用频率很高&#xff0c;且只用几条微指令即可完成的简单操作&#xff0c;可以把这部分简单操作的微指令序列固定下…

mfc140.dll丢失如何修复,分享多种有效的修复方法

在日常操作和使用电脑的过程中&#xff0c;我们可能会遇到一种较为常见的问题&#xff0c;即在尝试启动或运行某个应用程序时&#xff0c;系统突然弹出一个错误提示窗口&#xff0c;明确指出“mfc140.dll文件丢失”。这个mfc140.dll实际上是一个动态链接库文件&#xff08;DLL&…

linux运行ant 报错 Unable to locate tools.jar【已解决】

linux安装 ant 运行时报错 Unable to locate tools.jar. Expected to find it in /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.402.b06-1.el7_9.x86_64/lib/tools.jar 原因 已安装的jdk只有运行环境&#xff0c;没有tool.jar&#xff0c;而ant运行需要java开发环境&#xff0c;因…

不可思议!我的AI有道英语字典助手竟然与百度千帆AI应用创意挑战赛K12教育主题赛榜首作品差之毫厘

目录 一、前言二、效果对比三、优化《AI英语词典》提示词四、其他获奖作品链接 一、前言 今天看百度千帆AI原生应用创意挑战赛——K12教育主题赛&#xff0c;发现第一名的《我爱记单词》和我早两天发布的一篇《AI英语词典》的想法不谋而合。当时我们应该都是互相不知道对方的&a…

软件游戏缺失d3dcompiler_43.dll怎么修复?分享多种靠谱的解决方法

在我们日常频繁地操作和使用电脑的过程中&#xff0c;时常会遇到一些突发的技术问题。其中一种常见的情况是&#xff0c;在尝试启动或运行某个应用程序时&#xff0c;系统会弹出一个错误提示窗口&#xff0c;明确指出当前电脑环境中缺少了一个至关重要的动态链接库文件——d3dc…

Mysql全局优化总结

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

【MATLAB源码-第197期】基于matlab的粒子群算法(PSO)结合人工蜂群算法(ABC)无人机联合卡车配送仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 基于粒子群优化&#xff08;PSO&#xff09;算法的无人机联合卡车配送系统是一个高效的物流配送策略&#xff0c;旨在优化配送过程中的成本、时间和资源利用率。该系统融合了无人机和卡车的配送能力&#xff0c;通过智能算法…

MT2041 三角形的个数

思路&#xff1a;找规律&#xff0c;推公式 4等分&#xff1a; 头朝上的三角形&#xff1a; 边长为1&#xff1a;1234s1&#xff1b; 边长为2&#xff1a;123s2&#xff1b; 边长为3&#xff1a;12s3&#xff1b; 边长为4&#xff1a;1s4&#xff1b; 即si12...n-i1(n-i2)*(n-i…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(二)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 3 - 4节&#xff09; P3《开发准备-了解ArkTS》 鸿蒙开发主要是用来开发移动端应用的。 以前我们开发移动端应用的代码&#xff…

如何把视频中的画面保存为图片?免费的工具不用白不用

在数字化时代&#xff0c;截取视频中的珍贵瞬间成为了人们创作、分享和保存回忆的重要方式。 那么&#xff0c;如何迅速捕捉视频中的精彩画面&#xff0c;留存美好瞬间呢&#xff1f;有人说直接截图就可以&#xff0c;如果直接截图就可以&#xff0c;小编就不用写这篇文章了&a…

Tomcat源码解析——一次请求的处理流程

在上一篇文章中&#xff0c;我们知道Tomcat在启动后&#xff0c;会在Connector中开启一个Acceptor(接收器)绑定线程然后用于监听socket的连接&#xff0c;那么当我们发出请求时&#xff0c;第一步也就是建立TCP连接&#xff0c;则会从Acceptor的run方法处进入。 Acceptor&…

29 OpenCV 图像距

文章目录 距的概念API函数示例 距的概念 距的概念 API函数 moments( InputArray array,//输入数据 bool binaryImagefalse // 是否为二值图像 )contourArea( InputArray contour,//输入轮廓数据 bool oriented// 默认false、返回绝对值)arcLength( InputArray curve…

springboot结合elasticJob

先说一说什么是elasticJob。 ElasticJob是一个分布式任务调度的解决方案&#xff0c;它由俩个相互独立的子项目Elastic-job-lite和Elastic- job-cloud组成。 任务调度&#xff1a;是指系统为了自动完成特定任务&#xff0c;在任务的特定时刻去执行任务的过程。 分布式&#xf…

BUUCTF---misc---[SWPU2019]我有一只马里奥

1、下载附件是一个.exe文件 2、运行之后可以看到桌面生成了1.txt文件&#xff0c;文件里面有如下内容 3、经过信息搜索&#xff1a;NTFS&#xff08;New Technology File System&#xff09;是一种由Microsoft开发的专有日志文件系统。根据它的提示&#xff0c;应该是把flag.tx…