Making your own Linux Shell in C

07 Jun, 2020
To know more about what a shell is, click here.
We all use the built in terminal window in Linux distributions like Ubuntu, Fedora, etc. But how do they actually work? In this article, We are going to handle some under the hood features and algorithms what actually work inside a shell. All Linux operating systems have a terminal window to write in commands. But how are they executed properly after they are entered?Also, how are extra features like keeping the history of commands and showing help handled? All of this can be understood by creating your own shell.
The Basics
After a command is entered, the following things are done:
Command is entered and if length is non-null, keep it in history.Parsing : Parsing is the breaking up of commands into individual words and stringsChecking for special characters like pipes, etc is doneChecking if built-in commands are asked for.If pipes are present, handling pipes.Executing system commands and libraries by forking a child and calling execvp.Printing current directory name and asking for next input.
Command is entered and if length is non-null, keep it in history.
Parsing : Parsing is the breaking up of commands into individual words and strings
Checking for special characters like pipes, etc is done
Checking if built-in commands are asked for.
If pipes are present, handling pipes.
Executing system commands and libraries by forking a child and calling execvp.
Printing current directory name and asking for next input.
For keeping history of commands, recovering history using arrow keys and handling autocomplete using the tab key, we will be using the readline library provided by GNU.
Implementation
To install the readline library, open the terminal window and write
sudo apt-get install libreadline-dev
It will ask for your password. Enter it. Press y in the next step.
Printing the directory can be done using getcwd.
Getting user name can be done by getenv(“USER”)
Parsing can be done by using strsep(“”). It will separate words based on spaces. Always skip words with zero length to avoid storing of extra spaces.
After parsing, check the list of built-in commands, and if present, execute it. If not, execute it as a system command. To check for built-in commands, store the commands in an array of character pointers, and compare all with strcmp().Note: “cd” does not work natively using execvp, so it is a built-in command, executed with chdir().
For executing a system command, a new child will be created and then by using the execvp, execute the command, and wait until it is finished.
Detecting pipes can also be done by using strsep(“|”).To handle pipes, first separate the first part of the command from the second part. Then after parsing each part, call both parts in two separate new children, using execvp. Piping means passing the output of first command as the input of second command.Declare an integer array of size 2 for storing file descriptors. File descriptor 0 is for reading and 1 is for writing.Open a pipe using the pipe() function.Create two children.In child 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()
In child 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()
Wait for the two children to finish in the parent.
Declare an integer array of size 2 for storing file descriptors. File descriptor 0 is for reading and 1 is for writing.Open a pipe using the pipe() function.Create two children.In child 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()
In child 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()
Wait for the two children to finish in the parent.
Declare an integer array of size 2 for storing file descriptors. File descriptor 0 is for reading and 1 is for writing.
Open a pipe using the pipe() function.
Create two children.
In child 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()

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()

In child 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()

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()

Wait for the two children to finish in the parent.
// C Program to design a shell in Linux#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>#include<readline/readline.h>#include<readline/history.h>  #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 startupvoid 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 inputint 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 executedvoid 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 executedvoid 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 builtinvoid 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 commandsint 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 pipeint 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 wordsvoid 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;}
To Run the code –
 gcc shell.c -lreadline
./a.out 
Output:
This article is contributed by Suprotik Dey. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
top_gunn
system-programming
C Language
Linux-Unix
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Substring in C++
Multidimensional Arrays in C / C++
Function Pointer in C
Left Shift and Right Shift Operators in C/C++
Different Methods to Reverse a String in C++
Sed Command in Linux/Unix with examples
grep command in Unix/Linux
AWK command in Unix/Linux with examples
tar command in Linux with examples
UDP Server-Client implementation in C