📜  用C语言制作自己的Linux Shell

📅  最后修改于: 2021-05-25 20:25:45             🧑  作者: Mango

要了解有关什么是外壳的更多信息,请单击此处。

我们都使用Linux发行版(例如Ubuntu,Fedora等)中的内置终端窗口。但是它们实际上如何工作?在本文中,我们将处理一些外壳内部实际起作用的功能和算法。所有Linux操作系统都有一个终端窗口来写入命令。但是输入后如何正确执行?
另外,如何保留诸如保留命令历史记录和显示帮助之类的额外功能?所有这些都可以通过创建自己的shell来理解。


基础知识

输入命令后,将完成以下操作:

  1. 输入了命令,如果length不为null,则将其保留在历史记录中。
  2. 解析:解析是将命令分解为单个单词和字符串
  3. 检查特殊字符(例如管道等)是否已完成
  4. 检查是否要求内置命令。
  5. 如果有管道,请处理管道。
  6. 通过派生一个孩子并调用execvp来执行系统命令和库。
  7. 打印当前目录名称并要求下一次输入。

为了保留命令的历史记录,使用箭头键恢复历史记录以及使用Tab键处理自动完成功能,我们将使用GNU提供的readline库。

执行

要安装readline库,请打开终端窗口并编写

sudo apt-get install libreadline-dev

它将要求您输入密码。输入它。在下一步中按y。

  • 可以使用getcwd完成目录的打印。
  • 可以通过getenv(“ USER”)来获取用户名
  • 可以使用strsep(“”)进行解析。它将基于空格分隔单词。始终跳过长度为零的单词,以避免存储多余的空格。
  • 解析后,检查内置命令列表,如果有,请执行。如果不是,则将其作为系统命令执行。要检查内置命令,请将命令存储在字符指针数组中,然后将所有内容与strcmp()进行比较。
    注意:“ cd”不能在execvp上本地运行,因此它是一个内置命令,使用chdir()执行。
  • 为了执行系统命令,将创建一个新的子代,然后通过使用execvp来执行该命令,并等待其完成。
  • 也可以使用strsep(“ |”)来检测管道。要处理管道,请首先将命令的第一部分与第二部分分开。然后,在解析每个部分之后,使用execvp在两个单独的新子代中调用这两个部分。管道传输意味着将第一个命令的输出作为第二个命令的输入。
    1. 声明一个大小为2的整数数组,用于存储文件描述符。文件描述符0用于读取,文件描述符1用于写入。
    2. 使用pipe()函数打开管道。
    3. 创建两个孩子。
    4. 在孩子1->
      Here the output has to be taken into the pipe.
      Copy file descriptor 1 to stdout.
      Close  file descriptor 0.
      Execute the first command using execvp()
      
    5. 在孩子2->
      Here the input has to be taken from the pipe.
      Copy file descriptor 0 to stdin.
      Close file descriptor 1.
      Execute the second command using execvp()
      
    6. 等待两个孩子在父母中完成。
// C Program to design a shell in Linux
#include
#include
#include
#include
#include
#include
#include
#include
  
#define MAXCOM 1000 // max number of letters to be supported
#define MAXLIST 100 // max number of commands to be supported
  
// Clearing the shell using escape sequences
#define clear() printf("\033[H\033[J")
  
// Greeting shell during startup
void init_shell()
{
    clear();
    printf("\n\n\n\n******************"
        "************************");
    printf("\n\n\n\t****MY SHELL****");
    printf("\n\n\t-USE AT YOUR OWN RISK-");
    printf("\n\n\n\n*******************"
        "***********************");
    char* username = getenv("USER");
    printf("\n\n\nUSER is: @%s", username);
    printf("\n");
    sleep(1);
    clear();
}
  
// Function to take input
int takeInput(char* str)
{
    char* buf;
  
    buf = readline("\n>>> ");
    if (strlen(buf) != 0) {
        add_history(buf);
        strcpy(str, buf);
        return 0;
    } else {
        return 1;
    }
}
  
// Function to print Current Directory.
void printDir()
{
    char cwd[1024];
    getcwd(cwd, sizeof(cwd));
    printf("\nDir: %s", cwd);
}
  
// Function where the system command is executed
void execArgs(char** parsed)
{
    // Forking a child
    pid_t pid = fork(); 
  
    if (pid == -1) {
        printf("\nFailed forking child..");
        return;
    } else if (pid == 0) {
        if (execvp(parsed[0], parsed) < 0) {
            printf("\nCould not execute command..");
        }
        exit(0);
    } else {
        // waiting for child to terminate
        wait(NULL); 
        return;
    }
}
  
// Function where the piped system commands is executed
void execArgsPiped(char** parsed, char** parsedpipe)
{
    // 0 is read end, 1 is write end
    int pipefd[2]; 
    pid_t p1, p2;
  
    if (pipe(pipefd) < 0) {
        printf("\nPipe could not be initialized");
        return;
    }
    p1 = fork();
    if (p1 < 0) {
        printf("\nCould not fork");
        return;
    }
  
    if (p1 == 0) {
        // Child 1 executing..
        // It only needs to write at the write end
        close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        close(pipefd[1]);
  
        if (execvp(parsed[0], parsed) < 0) {
            printf("\nCould not execute command 1..");
            exit(0);
        }
    } else {
        // Parent executing
        p2 = fork();
  
        if (p2 < 0) {
            printf("\nCould not fork");
            return;
        }
  
        // Child 2 executing..
        // It only needs to read at the read end
        if (p2 == 0) {
            close(pipefd[1]);
            dup2(pipefd[0], STDIN_FILENO);
            close(pipefd[0]);
            if (execvp(parsedpipe[0], parsedpipe) < 0) {
                printf("\nCould not execute command 2..");
                exit(0);
            }
        } else {
            // parent executing, waiting for two children
            wait(NULL);
            wait(NULL);
        }
    }
}
  
// Help command builtin
void openHelp()
{
    puts("\n***WELCOME TO MY SHELL HELP***"
        "\nCopyright @ Suprotik Dey"
        "\n-Use the shell at your own risk..."
        "\nList of Commands supported:"
        "\n>cd"
        "\n>ls"
        "\n>exit"
        "\n>all other general commands available in UNIX shell"
        "\n>pipe handling"
        "\n>improper space handling");
  
    return;
}
  
// Function to execute builtin commands
int ownCmdHandler(char** parsed)
{
    int NoOfOwnCmds = 4, i, switchOwnArg = 0;
    char* ListOfOwnCmds[NoOfOwnCmds];
    char* username;
  
    ListOfOwnCmds[0] = "exit";
    ListOfOwnCmds[1] = "cd";
    ListOfOwnCmds[2] = "help";
    ListOfOwnCmds[3] = "hello";
  
    for (i = 0; i < NoOfOwnCmds; i++) {
        if (strcmp(parsed[0], ListOfOwnCmds[i]) == 0) {
            switchOwnArg = i + 1;
            break;
        }
    }
  
    switch (switchOwnArg) {
    case 1:
        printf("\nGoodbye\n");
        exit(0);
    case 2:
        chdir(parsed[1]);
        return 1;
    case 3:
        openHelp();
        return 1;
    case 4:
        username = getenv("USER");
        printf("\nHello %s.\nMind that this is "
            "not a place to play around."
            "\nUse help to know more..\n",
            username);
        return 1;
    default:
        break;
    }
  
    return 0;
}
  
// function for finding pipe
int parsePipe(char* str, char** strpiped)
{
    int i;
    for (i = 0; i < 2; i++) {
        strpiped[i] = strsep(&str, "|");
        if (strpiped[i] == NULL)
            break;
    }
  
    if (strpiped[1] == NULL)
        return 0; // returns zero if no pipe is found.
    else {
        return 1;
    }
}
  
// function for parsing command words
void parseSpace(char* str, char** parsed)
{
    int i;
  
    for (i = 0; i < MAXLIST; i++) {
        parsed[i] = strsep(&str, " ");
  
        if (parsed[i] == NULL)
            break;
        if (strlen(parsed[i]) == 0)
            i--;
    }
}
  
int processString(char* str, char** parsed, char** parsedpipe)
{
  
    char* strpiped[2];
    int piped = 0;
  
    piped = parsePipe(str, strpiped);
  
    if (piped) {
        parseSpace(strpiped[0], parsed);
        parseSpace(strpiped[1], parsedpipe);
  
    } else {
  
        parseSpace(str, parsed);
    }
  
    if (ownCmdHandler(parsed))
        return 0;
    else
        return 1 + piped;
}
  
int main()
{
    char inputString[MAXCOM], *parsedArgs[MAXLIST];
    char* parsedArgsPiped[MAXLIST];
    int execFlag = 0;
    init_shell();
  
    while (1) {
        // print shell line
        printDir();
        // take input
        if (takeInput(inputString))
            continue;
        // process
        execFlag = processString(inputString,
        parsedArgs, parsedArgsPiped);
        // execflag returns zero if there is no command
        // or it is a builtin command,
        // 1 if it is a simple command
        // 2 if it is including a pipe.
  
        // execute
        if (execFlag == 1)
            execArgs(parsedArgs);
  
        if (execFlag == 2)
            execArgsPiped(parsedArgs, parsedArgsPiped);
    }
    return 0;
}

运行代码–

gcc shell.c -lreadline
./a.out 

输出:

想要从精选的最佳视频中学习和练习问题,请查看《基础知识到高级C的C基础课程》。