文章目录
- 0.准备工作
- 1.大体框架
- 一、获取命令行
- 二、解析命令行
- 三、进程执行
- 1.普通命令
- 2.内建命令
- 四、完整代码:
0.准备工作
1.大体框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
//用于修饰命令行
//类似:[hh@VM-4-10-centos ~]$
#define LINE_SIZE 1024//输入命令最大长度
#define ARGC_SIZE 32//命令行参数表的大小
#define EXIT_CODE 44//退出码
int lastcode = 0;//上一次的退出码
int quit = 0
char commandline[LINE_SIZE];//输入的命令
char *argv[ARGC_SIZE];//解析后保存的命令
char pwd[LINE_SIZE];//保存当前所在路径
// 自定义环境变量表
char myenv[LINE_SIZE];
//因为环境变量表里面保存的不是
//变量本身,而是其地址,所以我们为了防止
//自己导入的环境变量被覆盖,需要自己维护一段空间
//这里myenv只能维护一个环境变量,因为只有一个地址
const char *getusername()
{//获取用户名
return getenv("USER");
}
const char *gethostname()
{//获取主机名
return getenv("HOSTNAME");
}
void getpwd()
{
//将当前路径保存到pwd中
getcwd(pwd, sizeof(pwd));
}
void interact(char *cline, int size){ }//获取命令行
int splitstring(char cline[], char *_argv[]){}//解析命令行
void NormalExcute(char *_argv[]){}//执行普通命令
int buildCommand(char *_argv[], int _argc){}//执行内键命令
int main()
{
while(!quit){
// 1.
// 2. 交互问题,获取命令行
interact(commandline, sizeof(commandline));
// 3. 子串分割的问题,解析命令行
int argc = splitstring(commandline, argv);
if(argc == 0) continue;
// 4. 指令的判断
//内键命令,本质就是一个shell内部的一个函数
int n = buildCommand(argv, argc);
// 5. 普通命令的执行
if(!n) NormalExcute(argv);
}
return 0;
}
一、获取命令行
在获取命令之前我们需要先建立一个命令行
类似这种效果:
void interact(char *cline, int size)
{
getpwd();//将当前路径写入到pwd中
printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);
fgets(cline, size, stdin);
//这里不用scanf的原因是其遇到空格与回车不会读取
//所以我们选择用fgets,将命令写入cline中
// "abcd\n\0"
//又因为fgets会读入回车键,所以我们要手动把回车位置改为'\0'
cline[strlen(cline)-1] = '\0';
}
二、解析命令行
int splitstring(char cline[], char *_argv[])
{
int i = 0;
//strtok用于分割字符串,上面我们宏定义了只有要DELIM中的字符
//就会发生分割,将分割后的字符串写入argv中
argv[i++] = strtok(cline, DELIM);
while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=
return i - 1;
//返回argv中存的字符串个数
}
三、进程执行
1.普通命令
void NormalExcute(char *_argv[])
{
pid_t id = fork();//创建子进程
if(id < 0){
perror("fork");
return;
}
else if(id == 0){
//让子进程执行命令
//execvp相当于一个加载器
//该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行
//会从环境变量中的路径中找到我们的可执行程序
execvp(_argv[0], _argv);
exit(EXIT_CODE);
}
else{
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
//等待子进程成功,更改退出码
lastcode = WEXITSTATUS(status);
}
}
}
2.内建命令
int buildCommand(char *_argv[], int _argc)
{
if(_argc == 2 && strcmp(_argv[0], "cd") == 0){
//chdir改变当前进程路径
//假如我们让子进程执行cd命令,子进程确实路径改变了
//但子进程执行完就被父进程回收了,没屁用
//因为进程的独立性我父进程路径不受影响。
//所以我们要手动修改路径
chdir(argv[1]);
getpwd();
//将新的路径写入pwd
//改变环境变量PWD,用pwd对其进行写入
sprintf(getenv("PWD"), "%s", pwd);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
//自己维护环境变量空间
strcpy(myenv, _argv[1]);//将环境变量放入自己的myenv
putenv(myenv);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){
if(strcmp(_argv[1], "$?") == 0)
{
printf("%d\n", lastcode);
lastcode=0;
}
else if(*_argv[1] == '$'){
//_argv[1]+1为$后面的值例如$PATH
//那么最后就获取PATH的值
char *val = getenv(_argv[1]+1);
if(val) printf("%s\n", val);
}
else{
printf("%s\n", _argv[1]);
}
return 1;
}
// 特殊处理一下ls
//因为ls中如果是可执行文件,其会显示为特殊颜色
if(strcmp(_argv[0], "ls") == 0)
{
_argv[_argc++] = "--color";
_argv[_argc] = NULL;
}
return 0;
}
四、完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];
// 自定义环境变量表
char myenv[LINE_SIZE];
const char *getusername()
{
return getenv("USER");
}
const char *gethostname()
{
return getenv("HOSTNAME");
}
void getpwd()
{
getcwd(pwd, sizeof(pwd));
}
void interact(char *cline, int size)
{
getpwd();
printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);
fgets(cline, size, stdin);
// "abcd\n\0"
cline[strlen(cline)-1] = '\0';
}
int splitstring(char cline[], char *_argv[])
{
int i = 0;
argv[i++] = strtok(cline, DELIM);
while(_argv[i++] = strtok(NULL, DELIM));
return i - 1;
}
void NormalExcute(char *_argv[])
{
pid_t id = fork();
if(id < 0){
perror("fork");
return;
}
else if(id == 0){
//让子进程执行命令
//execvpe(_argv[0], _argv, environ);
execvp(_argv[0], _argv);
exit(EXIT_CODE);
}
else{
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
lastcode = WEXITSTATUS(status);
}
}
}
int buildCommand(char *_argv[], int _argc)
{
if(_argc == 2 && strcmp(_argv[0], "cd") == 0){
chdir(argv[1]);
getpwd();
sprintf(getenv("PWD"), "%s", pwd);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
strcpy(myenv, _argv[1]);
putenv(myenv);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){
if(strcmp(_argv[1], "$?") == 0)
{
printf("%d\n", lastcode);
lastcode=0;
}
else if(*_argv[1] == '$'){
char *val = getenv(_argv[1]+1);
if(val) printf("%s\n", val);
}
else{
printf("%s\n", _argv[1]);
}
return 1;
}
// 特殊处理一下ls
if(strcmp(_argv[0], "ls") == 0)
{
_argv[_argc++] = "--color";
_argv[_argc] = NULL;
}
return 0;
}
int main()
{
while(!quit){
// 1.
// 2. 交互问题,获取命令行
interact(commandline, sizeof(commandline));
// 3. 子串分割的问题,解析命令行
int argc = splitstring(commandline, argv);
if(argc == 0) continue;
// 4. 指令的判断
//内键命令,本质就是一个shell内部的一个函数
int n = buildCommand(argv, argc);
// 5. 普通命令的执行
if(!n) NormalExcute(argv);
}
return 0;
}