[Linux]Написать минималистическую версию оболочки (версия 1)

[Linux]Напишите минималистическую версию оболочки-версии1.

Эта статья может помочь изучающим систему Linux лучше понять принципы реализации интерпретатора командной строки с точки зрения кода.

Печать командной строки

Когда операционная система Linux работает, процесс оболочки должен интерпретировать командную строку, а затем позволить системе выполнить соответствующую команду. Поэтому при печати приглашения командной строки используется метод печати в бесконечном цикле. Конкретная логика кода как следует:

int main()
{
    
    
  while(1)
  {
    
    
    printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));
    fflush(stdout);
    //sleep(200); -- 当前阶段用于演示效果
  }
  • Чтобы отличить его от системной оболочки, напечатаны два знака $.
  • Информация, выводимая из командной строки, получается через переменные среды, поэтому вам нужно всего лишь вызвать системный интерфейс, чтобы получить соответствующие переменные среды.
    • USERПеременная среды – текущее используемое имя пользователя.
    • HOSTNAMEПеременная среды – текущее имя хоста
    • PWDПеременная среды — абсолютный путь к местоположению текущего пользователя.
  • Поскольку монитор использует стратегию буферизации строк, буфер необходимо обновлять вручную, чтобы на экране отображалось приглашение командной строки.

Поскольку переменная среды PWD — это абсолютный путь текущего пользователя, вам необходимо написать функцию getpathдля получения имени текущего каталога. Конкретная логика кода выглядит следующим образом:

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;
}
  • Если длина строки абсолютного пути равна 1, это означает, что это корневой каталог, верните"/"
  • В других путях все пути перед текущим каталогом в абсолютном пути должны быть разделены, включая разделители путей. Например, текущая записанная переменная среды абсолютного пути — PWD=/home/ qxm/linux-warehouse/review/mybash/version1 , getpathи возвращаемое значение — это первый адрес строки version1 .

Демонстрация эффекта:

изображение-20230904145622641

Получить параметры командной строки

Чтобы принимать параметры командной строки, вам нужно всего лишь настроить массив строк для приема пользовательского ввода.Конкретная логика кода следующая:

#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;
}
  • Поскольку пользователь будет нажимать Enter при вводе, а это означает, что он появится в конце ввода '\n', его необходимо удалить при использовании параметров командной строки. Например, если пользователь вводит ls -a -l\n , команда параметру строки требуется только ls -a -l

Демонстрация эффекта:

изображение-20230904151119734

Интерпретация параметров командной строки

После получения параметров командной строки, введенных пользователем, параметры командной строки необходимо разделить на несколько строк в соответствии с пробелами ввода. Например, если пользователь вводит ls -a -l , они должны быть разделены на ls , -a , -l , в частности. Логика кода следующая:

#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;
}
  • Вызовите функцию библиотеки языка C strtok, чтобы получить первый адрес строки после разделения символа пробела, установите для пробела значение '\0'и сохраните первый адрес строки.

Демонстрация эффекта:

изображение-20230904154551480

Выполнить команду пользователя

Создайте дочерний процесс, замените его программой и позвольте дочернему процессу выполнить команду, введенную пользователем.Конкретная логика кода выглядит следующим образом:

#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;
}
  • Поскольку реализованная оболочка используется для выполнения системных команд и получения параметров командной строки записи массива, используется execvpзамена программы процесса.

Демонстрация эффекта:

демо-версия оболочки-1

Полный код

#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;
}

Примечание. Эта версия оболочки может выполнять только системные команды и не может поддерживать все системные команды, такие как команда cd.

рекомендация

отblog.csdn.net/csdn_myhome/article/details/132671651