Haciendo tu propio Shell de Linux en C

Para saber más sobre qué es una concha, haz clic aquí .

Todos usamos la ventana de terminal incorporada en las distribuciones de Linux como Ubuntu, Fedora, etc. Pero, ¿cómo funcionan realmente? En este artículo, vamos a manejar algunas características y algoritmos ocultos que realmente funcionan dentro de un shell. Todos los sistemas operativos Linux tienen una ventana de terminal para escribir comandos. Pero, ¿cómo se ejecutan correctamente después de ingresarlos?
Además, ¿cómo se manejan las funciones adicionales, como mantener el historial de comandos y mostrar ayuda? Todo esto se puede entender creando su propio caparazón.


Los basicos

Después de ingresar un comando, se hacen las siguientes cosas:

  1. Se ingresa el comando y si la longitud no es nula, manténgala en el historial.
  2. Análisis: el análisis es la división de comandos en palabras y strings individuales.
  3. Se realiza la verificación de caracteres especiales como tuberías, etc.
  4. Comprobando si se solicitan los comandos incorporados.
  5. Si hay tuberías , manipulación de tuberías.
  6. Ejecutar bibliotecas y comandos del sistema bifurcando a un niño y llamando a execvp .
  7. Imprime el nombre del directorio actual y solicita la siguiente entrada.

Para mantener el historial de comandos, recuperar el historial usando las teclas de flecha y manejar el autocompletado usando la tecla de tabulación, usaremos la biblioteca readline proporcionada por GNU.

Implementación

Para instalar la biblioteca readline, abra la ventana del terminal y escriba

sudo apt-get install libreadline-dev

Te pedirá tu contraseña. Ingresarlo. Presione y en el siguiente paso.

  • La impresión del directorio se puede hacer usando getcwd .
  • Getenv («USUARIO») puede obtener el nombre de usuario
  • El análisis se puede hacer usando strsep(“”) . Separará las palabras en función de los espacios. Omita siempre las palabras con longitud cero para evitar el almacenamiento de espacios adicionales.
  • Después de analizar, verifique la lista de comandos incorporados y, si está presente, ejecútelo. Si no, ejecútelo como un comando del sistema. Para verificar los comandos incorporados, almacene los comandos en una array de punteros de caracteres y compárelos con strcmp() .
    Nota: «cd» no funciona de forma nativa con execvp, por lo que es un comando integrado, ejecutado con chdir() .
  • Para ejecutar un comando del sistema, se creará un nuevo elemento secundario y luego, mediante el uso de execvp, ejecutará el comando y esperará hasta que finalice.
  • La detección de tuberías también se puede hacer usando strsep(“|”) . Para manejar tuberías, primero separe la primera parte del comando de la segunda parte. Luego, después de analizar cada parte, llame a ambas partes en dos nuevos hijos separados, usando execvp. Canalización significa pasar la salida del primer comando como la entrada del segundo comando.
    1. Declare una array de enteros de tamaño 2 para almacenar descriptores de archivos. El descriptor de archivo 0 es para lectura y 1 es para escritura.
    2. Abra una tubería usando la función pipe().
    3. Crea dos hijos.
    4. En niño 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. En niño 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. Espere a que los dos niños terminen en el padre.
// 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 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;
}

Para ejecutar el código –

 gcc shell.c -lreadline
./a.out 

Producción:

Este artículo es una contribución de Suprotik Dey . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo usando contribuya.geeksforgeeks.org o envíe su artículo por correo a contribuya@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *