Simulation implementation of Linux-Shell command line interpreter

Introduction: This article is mainly about simply implementing a shell command line interpreter, which can support basic common Linux commands, support built-in named echo and cd, and also support redirection operations!

1. Code analysis

1. Header file introduction:

Because the code is implemented under Linux, most of the header files introduced are Linux system calls, and it is recommended to use it in the Linux environment.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

These header files contain some library functions and system calls that need to be used.

2. Define some constants and global variables:

These constants and variables are used to store information such as command line input, command parameters, redirection types, and redirection files.

#define OPTION 64
#define NUM 1024
//Define redirection type
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
//Define a macro function to move the string pointer to the first character without spaces
#define trimSpace(start) do{\
  while(*start == ' '){\
      start + + ;}\
}while(0)
//Save the commands entered on the keyboard
char lineComend[NUM];
char* myargv[OPTION];//Define a char* pointer array to store parameters
int lastCode = 0;//The parent process records the last execution result of the child process
int lastSig = 0;
//Record whether it is a redirection and type
int redirType = NONE_REDIR;
char *redirFile = NULL;//Record redirection file name

3. Define an auxiliary function find_Redirect:

This function is used to find redirection symbols (> and <) in command line parameters and parse related redirection files and types.

void find_Redirect(char* argv){
  char* start = NULL;
  for(int i = 0;i<(int)strlen(argv);i + + ){
    if(argv[i] == '>'||argv[i]=='<'){
      if(argv[i]=='<'){
        //cat < log.txt
        argv[i] = '\0';
        //Remove the spaces after the redirection, keep only the file name part, and store it in the global variable redirFile
        start = argv + i + 1;
        trimSpace(start);
        redirFile = start;
        redirType = INPUT_REDIR;
      }else{
        if(argv[i + 1] == '>'){
          //cat xiaomi >> log.txt
          argv[i] = '\0';
          start = argv + i + 2;
          trimSpace(start);
          redirFile = start;
          redirType = APPEND_REDIR;
        }else{
          argv[i] = '\0';
          start = argv + i + 1;
          trimSpace(start);
          redirFile = start;
          redirType = OUTPUT_REDIR;
        }
      }
    }
  }
}

4. Main function

  • The main function contains an infinite loop, waiting for the user to enter a command in each loop, and handles logic such as command execution, redirection, and built-in commands.
  • At the beginning of the loop, the command prompt is output through printf, and then the command line input by the user is read using fgets and processed to remove the trailing newline character.
  • Next, call the find_Redirect function to find the redirection symbol and resolve the redirection file and type.
  • Then, use the strtok function to cut the input command, and store the cut command parameters into the myargv array. At the same time, some special built-in commands are processed, such as cd and echo.
  • For the echo command, output redirection is performed based on the redirection type.
  • If it is not a built-in command, a child process is created and the command is executed in the child process. Input and output redirection is performed based on the redirection type.
  • Finally, use waitpid to wait for the child process to complete execution and obtain the exit status code and signal of the child process.
int main(){
  //Initialize the redirection file and type at the beginning
  while(1){
  redirFile = NULL;
  redirType = NONE_REDIR;
    printf("[suhh@ziqiang address]$ ");
    fflush(stdout);
    //User input
    assert(fgets(lineComend,sizeof(lineComend)-1,stdin)!=NULL);
    //Clear the last one in the array\

    lineComend[strlen(lineComend)-1] = '\0';
   //ls -a -l
   //Cut the string by ' '
   find_Redirect(lineComend);
   myargv[0] = strtok(lineComend," ");
  // char * echo_str = NULL;
   if(strcmp(myargv[0],"echo")==0){
      myargv[1] = myargv[0] + strlen(myargv[0]) + 1;//Get the remaining string
      goto echo_start;
  }
   //strtok will put the rest into static variables, and only need to pass null next time it is called.
   int i = 1;
   //myargv[end]=NULL
   if(myargv[0]!=NULL & amp; & amp;(strcmp(myargv[0],"ls")==0||strcmp(myargv[0],"ll")==0)){
     myargv[i + + ] = (char*)"--color=auto";
     if(strcmp(myargv[0],"ll")==0) {
      myargv[i + + ] = (char*)"-l";
      myargv[0] = (char*)"ls";
    }
     //Let the ls command add color by default
   }
   while(myargv[i + + ] = strtok(NULL," ")){}
   if(strcmp(myargv[0],"cd")==0){
     //If it is a cd command, it cannot be executed by a child process. If executed by a child process, it will not affect the parent process and will not achieve the effect.
     //This is the built-in command at this time
     if(myargv[1]!=NULL)chdir(myargv[1]);
     continue;
   }
echo_start:
   if(myargv[0]!=NULL & amp; & amp; strcmp(myargv[0],"echo")==0){
     //This is also a built-in command
     //Determine redirection type
     int fd = 0;
     if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
        int flags = O_WRONLY | O_CREAT;
        if(redirType == APPEND_REDIR) flags = flags | O_APPEND;
        else flags = flags | O_TRUNC;
        fd = open(redirFile,flags,0666);
        //Back up the standard output stream first
        dup2(1,5);
        //redirect
        dup2(fd,1);
     }
     if(myargv[1]!=NULL & amp; & amp;strcmp(myargv[1],"$?")==0){
       printf("%d,%d\
",lastCode,lastSig);
     }else{
       if(myargv[1]!=NULL){
         //If the user enters echo "hello" remove ""
         if(myargv[1][0] =='"'){
           myargv[1] = (char*) &myargv[1][1];
         }
        int str_len = strlen(myargv[1]);
        if((myargv[1][str_len-1] == '"')||(myargv[1][str_len -2] == '"')){
         if(myargv[1][str_len-1] == '"') str_len --;
          else str_len -= 2;
        }
         for(int i = 0 ;i<str_len;i + + ){
           printf("%c",myargv[1][i]);
         }
         printf("\
");
       }
     }
     if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
       //Restore standard output stream
       close(fd);
       dup2(5,1);
     }
     continue;
   }
   pid_t it = fork();
   assert(it>=0);
   if(it == 0){
     //Judge redirection
     //cat < log.txt
     if(redirType == INPUT_REDIR){
         int rfd = open(redirFile,O_RDONLY);
         if(rfd == -1){
          perror("Error:open");
          exit(errno);
         }
         dup2(rfd,0);
     }else if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
      int flags = O_WRONLY | O_CREAT;
      if(redirType == OUTPUT_REDIR) flags |= O_TRUNC;
      else flags |= O_APPEND;
      int wfd = open(redirFile,flags,0666);
      assert(wfd>=0);
      (void)wfd;
      dup2(wfd,1);
     }
     //Subprocess executes process program replacement
     execvp(myargv[0],myargv);
     exit(1);
   }
   int status = 0;
   int wp = waitpid(it, & status,0); //Blocking and waiting
   assert(wp>=0);
   (void)wp;
   lastCode = (status>>8) & amp;0xFF;
   lastSig = status &0x7F;
   printf("exit_code:%d,exit_signal:%d\
",lastCode,lastSig);
  }
  return 0;
}

2. Knowledge points involved

1. fork() function

The fork function is a system call, used to create a child process, return the pid of the child process to the parent process, and return 0 to the child process.

  • After fork, there will be two processes executing the subsequent code respectively (in fact, in the body of the fork function, the parent and child processes have been generated, so there are two return values)
  • The child process has its own PCB and virtual address, which are the same as the parent process. That is to say, the contents of the virtual address of the parent process are copied to the child process.
  • Because the child process has the same virtual address as the parent process, reflected in the code, the child process can also “share” variables defined by the parent process, but if the child process wants to change its value, a copy-on-write operation will occur. The system will re-open a space in the physical memory, and then remap it to the virtual address through the page table. In other words, the child process thinks that the virtual address accessed is shared by the parent process, but it is actually another physical address.

2. Process program replacement

Load the specified process into the memory and replace the code segment and data segment of the original process, making the process think that it is executing its own code, but in fact it is executing someone else’s code.

Provides rich functions in C language for program replacement

Function prototype: int execl(const char *path, const char *arg, …);

Example: execl(“/user/bin/ls”,”ls”,NULL);

Explanation: The first parameter is to fill in the path of the program to be replaced.

The second parameter is to fill in how to execute this program.

The third parameter is to fill in the parameters of this program, and it must end with NULL.

Error returns -1

Function prototype: int execlp(const char *file, const char *arg, …);

Example: execlp(“ls”,”ls”,NULL);

Explanation: Based on the previous function prototype, a p is added, which represents path. At this time, there is no need to pass in the program address. You only need to tell the name of the program, and the executable program will be automatically set in the environment variable. Find.

Fill in the program name as the first parameter

The second parameter is to fill in how to execute this program.

The third parameter is to fill in the parameters of this program, and it must end with NULL.

Error returns -1

Function prototype: int execv(const char *path, char *const argv[])

Example: char* argv[] ={“ls”,”-a”,”-l”,”–color = auto”,NULL};

execv(“/user/bin/ls”,argv);

Explanation: This time a v is added, which can put all executable parameters into an array of string pointers and pass them uniformly.

The first parameter is where the program to be replaced is, the path

The second parameter is to fill in the string pointer array

Error returns -1

Function prototype: int execvp(const char *file, char *const argv[]);

Example: execvp(“ls”,argv);

Explanation: This one adds v and p and has the characteristics of the first two. The code in this article uses this

Fill in the program name as the first parameter

The second parameter is to fill in the string pointer array

Error returns -1

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Cloud native entry-level skills treeHomepageOverview 16942 people are learning the system