个人主页:敲上瘾-CSDN博客
目录
在实现shell的时候我们先创建自己myshell目录,在目录中创建myshell.cc文件,因为shell本来是用c语言写的,但为了方便我们这里使用c和c++混编。
首先我们做一个整体框架:
1.打印命令提示符
首先我们需要给用户显示提示信息,就像我们在使用shell时所看到的提示信息一样,如下:
对它进行一一分析:
所以我们可以这样定义一个宏:
#define ROOT_PROMPT "[%s@%s %s]# "
#define OTHER_PROMPT "[%s@%s %s]$ "
对于用户名,主机号我们可以通过getenv从环境变量中得到,但是获取当前路径我们不能使用getenv,因为我们myshell的环境变量使用的是父进程的环境变量,我们在当前使用cd命令切换路径环境变量中的pwd并不会有改变。
所以获取当前路径,我们可以使用getcwd,直接查询操作系统的文件系统,获取当前进程的工作目录的绝对路径。而不用依赖环境变量。当然我们最好单独设计一个GetPwd把它封装起来,这样也方便把当前路径加入到环境变量中。如下:
char* GetPwd()
{
char* const pwd=getcwd(cwd,sizeof(cwd));
if(pwd!=NULL)
{
snprintf(envpwd,sizeof(envpwd),"PWD=%s",pwd);
putenv(envpwd);
}
return pwd;
}
void Print()
{
string s=GetPwd();
string tmp;//去掉路径,只保留目录名
for(int i=s.size()-1;i>=0;i--)
{
if(s[i]=='/') break;
tmp.push_back(s[i]);
}
reverse(tmp.begin(),tmp.end());
if(strcmp(getenv("USER"),"root")==0)
printf(ROOT_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());
else
printf(OTHER_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());
}
2.获取用户输入指令
获取用户输入,因为用户输入的命令行参数是一个字符串,中间含有空格。所以我们不用scanf,cin进行输入。这里我们使用fgets。
bool GetCommand(char out[],int size)
{
char* c=fgets(out,size,stdin);
if(c==NULL) return false;
out[strlen(out)-1]=0;
if(strlen(out)==0) return false;
else return true;
}
3.重定向分析
在用户输入的指令中可能汉含有重定向操作,所以我们要提前特殊处理一下字符串,并把它做一个分割。
既然是重定向,也就是我们打开需要重向到的那个文件,所以我们需要获取打开方式和文件名。
重定向有三种:
- <:输入重定向(以读的方式打开文件)
- >:输出重定向(以写的方式打开文件)
- >>:追加重定向(以追加的方式打开文件)
所以我们可以使用宏来标记这些情况。
//重定向
#define NONE_REDIR 0//无重定向操作
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir=NONE_REDIR;//记录重定向情况
string filename;//记录需要重定向到的文件
bool AnalyseRedir(char arr[])
{
redir=NONE_REDIR;//这里还保留着上次执行的状态,需要初始化一下
filename.clear();//同理,保留着上次执行的文件名,需要清理
int end=strlen(arr)-1;
while(end>=0)
{
if(arr[end]=='<')
{
redir=INPUT_REDIR;
arr[end++]=0;
filename = GetFileName(arr,end);
//这里的文件名就是end以后从非空格的部分开始到结束,这里就不再展示,下面会给出源码
break;
}
else if(arr[end]=='>')
{
if(end-1>=0&&arr[end-1]=='>')
{
redir=APPEND_REDIR;
arr[end-1]=0;
//使后面再做命令行参数分析时,方便与后面内容分开
}
else redir=OUTPUT_REDIR;
filename = GetFileName(arr,end+1);
arr[end] = 0;
break;
}
else end--;
}
return true;
}
4.命令行参数表与环境变量表
在shell中有两张表命令行参数表和环境变量表,实质都是字符串数组。
- 命令行参数表:用来储存用户输入的命令行参数。
- 环境变量表:用来储存当前进程的属性和状态。
所以我们可以这样做一个全局变量:
#define MAXARGV 128
#define MAXENV 200
//命令行参数表
char* g_argv[MAXARGV];
int g_argc=0;
//环境变量表
char* g_env[MAXENV];
int g_envc=0;
环境变量表需要我们在程序启动时就将它导入, 当然程序启动后环境变量默认是父进程的,所以我们可以重新开辟空间把原环境变量的数据拷贝过来,然后再把environ更新为新的地址。具体实现请参考下文源码。
5.命令解析
刚才我们获取到了用户的输入得到一个字符串,需要把它一个一个按空格分开,来得到一张命令行参数表。方便后面做进程替换。
//命令行参数表
char* g_argv[MAXARGV];
int g_argc=0;
void AnalyseCommand(char* out)
{
#define SYMBOL " "
g_argc=0;
g_argv[g_argc++]=strtok(out,SYMBOL);
while((bool)(g_argv[g_argc++]=strtok(nullptr,SYMBOL)));
g_argv[g_argc]=0;
}
6.命令执行
6.1.创建子进程
shell执行命令的实质就是进程替换,我们在做进程替换的时候不想结束父进程,那么需要我们创建一个新的子进程,让子进程来做替换。
6.2.文件重定向
- pcd文件数组:储存了这个pcb打开的所有文件信息地址。
- 文件描述符(记fd):pcb的文件数组中的一个下标,0下标的文件为标准输入流,1下标的文件为标准输出流,2下标的文件为标准错误流,这三个都是系统默认打开的文件。
- 重定向:系统在对文件进行操作时只认fd,所以重定向的实质就是一个fd位置的信息被其他fd的信息覆盖。
>,>>,指的都是从原来的标准输出(fd=1)重定向到某个文件,< 从原来的标准输入(fd=0)重定向到某个文件。所以这里我们只需要打开新的文件并获取到它的fd,然后使用dup2把新文件的地址信息覆盖到fd=1的文件上就行。然后关闭新文件的fd。
//重定向文件打开+dup2
if(redir!=NONE_REDIR)
{
int fd=-1;
if(redir==INPUT_REDIR)
{
fd=open(filename.c_str(),O_RDONLY);
if(!(fd>=0)) exit(1);
dup2(fd,0);
}
else if(redir==OUTPUT_REDIR)
{
fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_TRUNC,0666);
if(!(fd>=0)) exit(1);
dup2(fd,1);
}
else
{
fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);
if(!(fd>=0)) exit(1);
dup2(fd,1);
}
close(fd);
}
6.3.处理内建命令
有一些命令比如cd,是一个内建命令,子进程是无法完成的,需要系统来执行。我们可以使用chdir来完成,chdir函数声明如下:
int chdir(const char *path);
它的作用是进入某个目录,需要传一个目录的路径。
7.源码
注意:在做编译链接时需要用指令g++,并且加入-std=c++11选项
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<sys/types.h>
#include<unistd.h>
#include<vector>
#include<cstdlib>
#include<sys/wait.h>
#include<algorithm>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;
#define COMMAND_SIZE 1024
#define MAXARGV 128
#define MAXENV 200
#define ROOT_PROMPT "[%s@%s %s]# "
#define OTHER_PROMPT "[%s@%s %s]$ "
//重定向
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir=NONE_REDIR;
string filename;
//命令行参数表
char* g_argv[MAXARGV];
int g_argc=0;
//环境变量表
char* g_env[MAXENV];
int g_envc=0;
//路径记录
char cwd[1024];
char envpwd[1024];
char* GetPwd()
{
char* const pwd=getcwd(cwd,sizeof(cwd));
if(pwd!=NULL)
{
snprintf(envpwd,sizeof(envpwd),"PWD=%s",pwd);
putenv(envpwd);
}
return pwd;
}
bool GetCommand(char out[],int size)
{
char* c=fgets(out,size,stdin);
if(c==NULL) return false;
out[strlen(out)-1]=0;
if(strlen(out)==0) return false;
else return true;
}
string GetFileName(char arr[],int p)
{
if(arr[p]==' ') p++;
return string(arr+p);
}
void PushEnv()
{
for(int i=0;environ[i];i++)
{
g_env[i]=(char*)malloc(strlen(environ[i])+1);
strcpy(g_env[i],environ[i]);
g_envc++;
}
g_env[g_envc]=NULL;
for(int i=0;g_env[i];i++)
putenv(g_env[i]);
environ=g_env;
}
void Print()
{
string s=GetPwd();
string tmp;
for(int i=s.size()-1;i>=0;i--)
{
if(s[i]=='/') break;
tmp.push_back(s[i]);
}
reverse(tmp.begin(),tmp.end());
if(strcmp(getenv("USER"),"root")==0)
printf(ROOT_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());
else
printf(OTHER_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());
}
bool AnalyseRedir(char arr[])
{
redir=NONE_REDIR;
filename.clear();
int end=strlen(arr)-1;
while(end>=0)
{
if(arr[end]=='<')
{
redir=INPUT_REDIR;
arr[end++]=0;
filename = GetFileName(arr,end);
break;
}
else if(arr[end]=='>')
{
if(end-1>=0&&arr[end-1]=='>')
{
redir=APPEND_REDIR;
arr[end-1]=0;
}
else
{
redir=OUTPUT_REDIR;
}
filename = GetFileName(arr,end+1);
arr[end] = 0;
break;
}
else end--;
}
return true;
}
void AnalyseCommand(char* out)
{
#define SYMBOL " "
g_argc=0;
g_argv[g_argc++]=strtok(out,SYMBOL);
while((bool)(g_argv[g_argc++]=strtok(nullptr,SYMBOL)));
g_argv[g_argc]=0;
}
bool Cd()
{
char* where;
if(g_argc==1)
where=getenv("HOME");
else
where=g_argv[1];
chdir(where);
return true;
}
bool Echo()
{
//... ...
return true;
}
void RunCmd()
{
pid_t id=fork();
if(id==0)
{
//重定向文件打开+dup2
if(redir!=NONE_REDIR)
{
int fd=-1;
if(redir==INPUT_REDIR)
{
fd=open(filename.c_str(),O_RDONLY);
if(!(fd>=0)) exit(1);
dup2(fd,0);
}
else if(redir==OUTPUT_REDIR)
{
fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_TRUNC,0666);
if(!(fd>=0)) exit(1);
dup2(fd,1);
}
else
{
fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);
if(!(fd>=0)) exit(1);
dup2(fd,1);
}
close(fd);
}
string tmp="cd";
if(g_argv[0]==tmp)
{
if(!Cd()) printf("-myshell: command not found\n");
}
else
{
execvp(g_argv[0],g_argv);
printf("-myshell: %s: command not found\n",g_argv[0]);
exit(1);
}
}
pid_t p=waitpid(id,NULL,0);
(void)p;
}
int main()
{
//载入环境变量
PushEnv();
while(1)
{
//命令行提示打印
Print();
//获取用户输入命令
char out[COMMAND_SIZE];
GetCommand(out,sizeof(out));
//重定向解析
AnalyseRedir(out);
//命令解析
AnalyseCommand(out);
//命令执行
RunCmd();
}
return 0;
}