[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 .
Демонстрация эффекта:
Получить параметры командной строки
Чтобы принимать параметры командной строки, вам нужно всего лишь настроить массив строк для приема пользовательского ввода.Конкретная логика кода следующая:
#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
Демонстрация эффекта:
Интерпретация параметров командной строки
После получения параметров командной строки, введенных пользователем, параметры командной строки необходимо разделить на несколько строк в соответствии с пробелами ввода. Например, если пользователь вводит 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'
и сохраните первый адрес строки.
Демонстрация эффекта:
Выполнить команду пользователя
Создайте дочерний процесс, замените его программой и позвольте дочернему процессу выполнить команду, введенную пользователем.Конкретная логика кода выглядит следующим образом:
#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
замена программы процесса.
Демонстрация эффекта:
Полный код
#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.