shell
- 自定义shell
- 目标
- 普通命令和内建命令(补充)
- shell实现
- 实现原理
- 实现代码
自定义shell
目标
- 能处理普通命令
- 能处理内建命令
- 要能帮助我们理解内建命令/本地变量/环境变量这些概念
- 理解shell的运行
普通命令和内建命令(补充)
在Linux系统中,命令大致可以分为普通命令(通常指的是外部命令)和内建命令两大类。以下是关于这两类命令的详细解释:
一、普通命令(外部命令)
定义:普通命令,也被称为外部命令,是Linux系统中的实用程序部分。这些命令通常以单独的程序文件形式存在,并存储在系统的特定目录中(如/bin、/usr/bin、/sbin、/usr/sbin等)。
特点:
功能强大:由于外部命令通常是独立的程序,因此它们可以包含复杂的功能和算法。
不随系统一起加载:在系统启动时,外部命令并不会被加载到内存中。它们只在被需要时,由shell程序通过PATH环境变量查找并加载到内存中执行。
执行速度相对较慢:由于需要查找和加载程序文件,以及创建子进程来执行命令,因此外部命令的执行速度通常比内建命令慢。
示例:常见的外部命令包括ls(列出目录内容)、vi(文本编辑器)、grep(文本搜索工具)等。
二、内建命令
定义:内建命令是shell程序的一部分,这些命令被直接集成在shell的源代码中,并随着shell程序的启动而被加载到内存中。
特点:
执行速度快:由于内建命令是在shell程序内部执行的,因此它们不需要创建子进程或查找外部程序文件,从而提高了执行速度。
与shell紧密集成:内建命令与shell程序紧密集成,因此它们可以更方便地访问和操作shell的环境变量、函数等。
占用内存较少(但相对外部命令而言):虽然内建命令的执行速度更快,但它们通常会占用一定的内存空间。然而,由于这些命令是shell程序的一部分,因此它们所占用的内存空间通常是可以接受的。
示例:常见的内建命令包括cd(切换目录)、pwd(显示当前工作目录)、echo(输出字符串到标准输出)、history(显示命令历史记录)等。
三、区分方法
在Linux中,可以使用type命令来区分一个命令是内建命令还是外部命令。例如:
输入type cd,输出结果为cd is a shell builtin,表示cd是一个内建命令。
输入type ls,输出结果为ls is aliased to ‘ls --color=auto’(或类似信息),表示ls是一个外部命令(尽管这里显示了别名信息,但可以通过进一步使用type -t ls或查看其路径来确认其为外部命令)。
shell实现
实现原理
⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时间的流逝从左向右移动。shell从⽤户读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
Shell的作用流程
- 读取输入:
Shell等待用户输入命令。这可以是直接在命令行提示符下输入的命令,也可以是来自脚本文件的命令。 - 解析命令:
Shell解析用户输入的命令,包括命令本身、参数和选项。它会检查命令的语法是否正确,以及命令是否存在。 - 查找命令:
Shell判断命令是内置命令还是外部命令。如果是内置命令,则直接执行;如果是外部命令,则在PATH环境变量中查找相应的程序文件。 - 执行命令:
Shell将解析后的命令传递给操作系统内核执行。内核会读取命令,并执行相应的操作。这包括调用系统资源、管理文件、执行程序等。 - 返回输出:
当命令执行完成后,Shell会返回执行结果或错误信息给用户。这可以是命令的输出内容、状态码等。 - 环境变量:
Shell使用环境变量来存储系统配置和用户信息。这些变量在命令执行和脚本编写中起着重要作用。例如,PATH变量指定了系统在哪些目录下查找可执行文件。 - 重定向和管道:
Shell支持将命令的输入和输出重定向到文件或其他设备上。同时,它还支持管道操作,可以将一个命令的输出作为下一个命令的输入。
实现代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
using namespace std;
const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令⾏参数表
char *gargv[argvnum];
int gargc = 0;
// 全局的变量
int lastcode = 0;
// 我的系统的环境变量
char *genv[envnum];
// 全局的当前shell⼯作路径
char pwd[basesize];
char pwdenv[basesize];
string GetUserName()
{
string name = getenv("USER");
return name.empty() ? "None" : name;
}
string GetHostName()
{
string hostname = getenv("HOSTNAME");
return hostname.empty() ? "None" : hostname;
}
string GetPwd()
{
if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";
snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);
putenv(pwdenv); // PWD=XXX
return pwd;
//string pwd = getenv("PWD");
//return pwd.empty() ? "None" : pwd;
}
string LastDir()
{
string curr = GetPwd();
if(curr == "/" || curr == "None") return curr;
// /home/whb/XXX
size_t pos = curr.rfind("/");
if(pos == std::string::npos) return curr;
return curr.substr(pos+1);
}
string MakeCommandLine()
{
// [whb@bite-alicloud myshell]$
char command_line[basesize];
snprintf(command_line, basesize, "[%s@%s %s]# ",\
GetUserName().c_str(), GetHostName().c_str(),LastDir().c_str());
return command_line;
}
void PrintCommandLine() // 1. 命令⾏提⽰符
{
printf("%s", MakeCommandLine().c_str());
fflush(stdout);
}
bool GetCommandLine(char command_buffer[], int size) //2. 获取⽤⼾命令
{
// 我们认为:我们要将⽤⼾输⼊的命令⾏,当做⼀个完整的字符串
// "ls -a -l -n"
char *result = fgets(command_buffer, size, stdin);
if(!result)
{
return false;
}
command_buffer[strlen(command_buffer)-1] = 0;
if(strlen(command_buffer) == 0) return false;
return true;
}
void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{
(void)len;
memset(gargv, 0, sizeof(gargv));
gargc = 0;
// "ls -a -l -n"
const char *sep = " ";
gargv[gargc++] = strtok(command_buffer, sep);
// =是刻意写的
while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
gargc--;
}
void debug()
{
printf("argc: %d\n", gargc);
for(int i = 0; gargv[i]; i++)
{
printf("argv[%d]: %s\n", i, gargv[i]);
}
}
bool ExecuteCommand() // 4. 执⾏命令
{
// 让⼦进程进⾏执⾏
pid_t id = fork();
if(id < 0) return false;
if(id == 0)
{
//⼦进程
// 1. 执⾏命令
execvpe(gargv[0], gargv, genv);
// 2. 退出
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
if(WIFEXITED(status))
{
lastcode = WEXITSTATUS(status);
}
else
{
lastcode = 100;
}
return true;
}
return false;
}
void AddEnv(const char *item)
{
int index = 0;
while(genv[index])
{
index++;
}
genv[index] = (char*)malloc(strlen(item)+1);
strncpy(genv[index], item, strlen(item)+1);
genv[++index] = nullptr;
}
// shell⾃⼰执⾏命令,本质是shell调⽤⾃⼰的函数
bool CheckAndExecBuiltCommand()
{
if(strcmp(gargv[0], "cd") == 0)
{
// 内建命令
if(gargc == 2)
{
chdir(gargv[1]);
lastcode = 0;
}
else
{
lastcode = 1;
}
return true;
}
else if(strcmp(gargv[0], "export") == 0)
{
// export也是内建命令
if(gargc == 2)
{
AddEnv(gargv[1]);
lastcode = 0;
}
else
{
lastcode = 2;
}
return true;
}
else if(strcmp(gargv[0], "env") == 0)
{
for(int i = 0; genv[i]; i++)
{
printf("%s\n", genv[i]);
}
lastcode = 0;
return true;
}
else if(strcmp(gargv[0], "echo") == 0)
{
if(gargc == 2)
{
// echo $?
// echo $PATH
// echo hello
if(gargv[1][0] == '$')
{
if(gargv[1][1] == '?')
{
printf("%d\n", lastcode);
lastcode = 0;
}
}
else
{
printf("%s\n", gargv[1]);
lastcode = 0;
}
}
else
{
lastcode = 3;
}
return true;
}
return false;
}
void InitEnv()
{
extern char **environ;
int index = 0;
while(environ[index])
{
genv[index] = (char*)malloc(strlen(environ[index])+1);
strncpy(genv[index], environ[index], strlen(environ[index])+1);
index++;
}
genv[index] = nullptr;
}
int main()
{
InitEnv();
char command_buffer[basesize];
while(true)
{
PrintCommandLine(); // 1. 命令⾏提⽰符
// command_buffer -> output
if( !GetCommandLine(command_buffer, basesize) ) // 2. 获取⽤⼾命令
{
continue;
}
//printf("%s\n", command_buffer);
ParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令
if ( CheckAndExecBuiltCommand() )
{
continue;
}
ExecuteCommand(); // 4. 执⾏命令
}
return 0;
}