[Linux]Write a minimalist version of the shell (version 1)

[Linux]Write a minimalist version of shell-version1

This article can help Linux system learners better understand the implementation principles of the command line interpreter from the perspective of code.

Command line prompt printing

When the Linux operating system is running, the shell process is required to interpret the command line, and then let the system complete the corresponding command. Therefore, when printing the command line prompt, an infinite loop printing method is used. The specific code logic is as follows:

int main()
{
    
    
  while(1)
  {
    
    
    printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));
    fflush(stdout);
    //sleep(200); -- 当前阶段用于演示效果
  }
  • In order to distinguish it from the system shell, two $ signs are printed.
  • The information printed by the command line prompt is obtained through environment variables, so you only need to call the system interface to obtain the corresponding environment variables.
    • USEREnvironment variable – the current username in use
    • HOSTNAMEEnvironment variable – current hostname
    • PWDEnvironment variable – the absolute path of the current user’s location
  • Since the monitor uses a line buffering strategy, the buffer needs to be refreshed manually to allow the command line prompt to be displayed on the screen.

Since the environment variable PWD is the absolute path of the current user, you need to write a function getpathto get the name of the current directory. The specific code logic is as follows:

const char* getpath(char* path)
{
    
    
  int length = strlen(path);
  if(length == 1) return "/"; //根目录
  int i = length - 1;
  while((path[i] != '/')) i--;
  return path+i+1;
}
  • If the string length of the absolute path is 1, it means that the location is the root directory, return"/"
  • In other paths, all paths before the current directory in the absolute path must be split, including the path separator. For example, the current recorded absolute path environment variable is PWD=/home/qxm/linux-warehouse/review/mybash/version1 , getpathand the return The value is the first address of the verson1 string

Effect demonstration:

image-20230904145622641

Receive command line parameters

To accept command line parameters, you only need to set up a string array to receive user input. The specific code logic is as follows:

#define MAX 1024

int main()
{
    
    
  while(1)
  {
    
    
	char commandstr[MAX] = {
    
     0 }; //接收命令行参数
    printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));
    fflush(stdout);
    char* s = fgets(commandstr, sizeof(commandstr), stdin);
    commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'
    //printf("%s\n", commandstr); -- 当前阶段用于演示效果
  }
  return 0;
}
  • Since the user will press Enter when typing, which means that one will appear at the end when typing '\n', it needs to be removed when using command line parameters. For example, if the user inputs ls -a -l\n , the command line parameter only needs ls -a -l

Effect demonstration:

image-20230904151119734

Interpret command line parameters

After obtaining the command line parameters input by the user, the command line parameters need to be split into multiple strings according to the input spaces. For example, if the user inputs ls -a -l , it should be split into ls , -a , -l , specifically The code logic is as follows:

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char commandstr[], char* argv[])//命令行参数解释
{
    
    
  argv[0] = strtok(commandstr, SEP);
  if (argv[0] == NULL) return -1; //字符串为空
  int i = 1;
    while(1)
    {
    
    
        argv[i] = strtok(NULL, SEP);
        if(argv[i] == NULL) break;
        i++;
    }
  return 0;
}

void debugPrint(char* argv[])//--当前阶段用于演示效果
{
    
    
  int i = 0;
  for (i = 0; argv[i] != NULL; i++)
  {
    
    
    printf("%s\n", argv[i]);
  }
}

int main()
{
    
    
  while(1)
  {
    
    
    char commandstr[MAX] = {
    
     0 }; //接收命令行参数
    char* argv[ARGC] = {
    
     NULL }; //存储命令行参数
    printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));
    fflush(stdout);
    char* s = fgets(commandstr, sizeof(commandstr), stdin);
    commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'
    
    int n = split(commandstr, argv);
    if(n != 0) continue; //用户输入空串
    //debugPrint(argv);  -- 当前阶段用于演示效果
  }
  return 0;
}
  • Call the C language library function strtokto obtain the first address of the string after splitting the space character, set the space to '\0', and store the first address of the string.

Effect demonstration:

image-20230904154551480

Execute user command

Create a child process, replace the process with the program, and let the child process complete the command entered by the user. The specific code logic is as follows:

#define MAX 1024
#define ARGC 64
#define SEP " "

const char* getpath(char* path)
{
    
    
  int length = strlen(path);
  if(length == 1) return "/"; //根目录
  int i = length - 1;
  while((path[i] != '/')) i--;
  return path+i+1;
}

int split(char commandstr[], char* argv[])
{
    
    
  argv[0] = strtok(commandstr, SEP);
  if (argv[0] == NULL) return -1; //字符串为空
  int i = 1;
    while(1)
    {
    
    
        argv[i] = strtok(NULL, SEP);
        if(argv[i] == NULL) break;
        i++;
    }
  return 0;
}

int main()
{
    
    
  while(1)
  {
    
    
    char commandstr[MAX] = {
    
     0 }; //接收命令行参数
    char* argv[ARGC] = {
    
     NULL };  //存储命令行参数
    printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));
    fflush(stdout);
    char* s = fgets(commandstr, sizeof(commandstr), stdin);
    commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'
    int n = split(commandstr, argv);
    if(n != 0) continue; //用户输入空串
      
    pid_t id = fork();//创建子进程完成命令执行
    if (id == 0)
    {
    
    
      //子进程
      execvp(argv[0], argv); //进程程序替换
      exit(0);
    }
    
    int status = 0;
    waitpid(id, &status, 0);//回收子进程
  }
  return 0;
}
  • Since the implemented shell is used to execute system commands and obtain an array recording command line parameters, execvpprocess program replacement is used

Effect demonstration:

shell-1 demo

Complete code

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

#define MAX 1024
#define ARGC 64
#define SEP " "

const char* getpath(char* path)
{
    
    
  int length = strlen(path);
  if(length == 1) return "/"; //根目录
  int i = length - 1;
  while((path[i] != '/')) i--;
  return path+i+1;
}

int split(char commandstr[], char* argv[])
{
    
    
  assert(commandstr);
  assert(argv);
    
  argv[0] = strtok(commandstr, SEP);
  if (argv[0] == NULL) return -1; //字符串为空
  int i = 1;
    while(1)
    {
    
    
        argv[i] = strtok(NULL, SEP);
        if(argv[i] == NULL) break;
        i++;
    }
  return 0;
}

int main()
{
    
    
  while(1)
  {
    
    
    char commandstr[MAX] = {
    
     0 }; //接收命令行参数
    char* argv[ARGC] = {
    
     NULL };  //存储命令行参数
    printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));
    fflush(stdout);
    char* s = fgets(commandstr, sizeof(commandstr), stdin);
    assert(s); //对fgets函数的结果断言
    (void)s;//保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用
    commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'
    int n = split(commandstr, argv);
    if(n != 0) continue; //用户输入空串
    pid_t id = fork();
    assert(id >= 0);
    (void)id;
    if (id == 0)
    {
    
    
      //子进程
      execvp(argv[0], argv); 
      exit(0);
    } 
    int status = 0;
    waitpid(id, &status, 0);
  }
  return 0;
}
);
    if(n != 0) continue; //用户输入空串
    pid_t id = fork();
    assert(id >= 0);
    (void)id;
    if (id == 0)
    {
    
    
      //子进程
      execvp(argv[0], argv); 
      exit(0);
    } 
    int status = 0;
    waitpid(id, &status, 0);
  }
  return 0;
}

Note: This version of the shell can only execute system commands, and cannot support all system commands such as the cd command.

Guess you like

Origin blog.csdn.net/csdn_myhome/article/details/132671651
Recommended