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 usingfgets
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 themyargv
array. At the same time, some special built-in commands are processed, such ascd
andecho
.- 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