Linux--实现一个简易的Shell(可以获取命令提示符,支持管道)
Shell的实现过程
1.从标准输入获取命令行;
2.对用户输入的命令行进行解析,解析出要执行的指令和参数;
3.创建一个子进程;
4.子进程进行程序替换,父进程等待;
5.当子进程执行完毕,父进程从wait中返回,继续下一次循环。
shell命令提示符
(1)一般的shell输入命令之前都会有命令提示符:
[root@localhost 进程替换]#
root为用户名,localhost为主机名,后面跟着的是当前目录的相对路径;
(2)所以在自己的Shell里面也可以输出这个命令提示符:
①获取用户名
getpwuid根据传入的用户ID,返回指向passwd的结构体,该结构体初始化了里面的所有成员.用户的ID通过getuid( )函数获得。
//passwd结构体
struct passwd
{
char *pw_name; /* 用户名*/
char *pw_passwd; /* 密码.*/
__uid_t pw_uid; /* 用户ID.*/
__gid_t pw_gid; /*组ID.*/
char *pw_gecos; /*真实名*/
char *pw_dir; /* 主目录.*/
char *pw_shell; /*使用的shell*/
};
用于获取用户名:
struct passwd *pwd; //定义一个passwd类型的指针pwd
pwd = getpwuid(getuid());
pwd->pw_name;
②获取主机名:(通过gethostname函数)
//获得主机名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname)); //hostname里面存放的就是主机名
③获取相对路径
getcwd( )函数用于获取当前目录的绝对路径,但是shell里命令提示符的后面是相对路径,路径使用/进行分隔,所以最后一个/后面的内容为相对路径。
//char * strtok ( char * str, const char * delimiters );
Parse函数用于路径的切分,将路径按照”/”进行切分,将切分后的结果保存在字符串数组output里面。
void Parse(char* input, char* output[])//用于将路径进行切分,最后一个/后面为当前路径
{
char* p = strtok(input,"/");
int i = 0;
while(p!= NULL)
{
output[i++] = p;
p=strtok(NULL,"/");
}
}
main函数中获取相对路径的有关代码:
char path[100] = {0};
getcwd(path,sizeof(path)); //getcwd获得的是绝对路径(path里面存放的是绝对路径)
char* path2[100] = {0};
Parse(path,path2); //Parse函数用于切分路径,将路径按照"/"进行切分
char* path3 = NULL; //path里面为绝对路径,将path按照/进行分割,分割的结果保存在path2里面,path3里面保存最后一个字符串,即相对路>径
int i = 0;
while(path2[i] !=NULL)
{
path3= path2[i];
i++;
}
从标准输入获取命令
定义一个buf数组用于存放输入的指令(指令是字符串的形式);
在main函数中编写如下代码,用于获取命令行:
char buf[1024] = {0};
fgets(buf);
对用户输入的命令行进行解析
通过ParseArg函数对指令按照空格进行切分,将切分出来的单独的字符串放入output数组里面。(切分用到字符串处理函数strtok)
void ParseArg(char* input, char* output[]) //用于对输入的命令进行切分
{
char* p = strtok(input," ");
int output_size = 0;
while(p != NULL)
{
output[output_size++] = p;
p = strtok(NULL," ");
}
output[output_size] = NULL;
}
在main函数中编写如下代码:
//解析字符串,解析出指令和参数
char* argv[100] = {};
ParseArg(buf,argv); //ParseArg函数用于对命令行进行切分,buf存放从标准输入获取的指令,argv存放解析出来的命令参数
创建一个子进程,子进程进行程序替换,父进程等待
用Exec函数进行封装,Exec函数里面创建一个子进程,子进程进行程序替换,父进程进行等待(父进程不关心子进程的退出状态,所以可以采用wait(NULL))
void Exec(char* argv[]) //创建子进程执行进程程序替换
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child
execvp(argv[0],argv);
//execvp失败会执行下面的语句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
Shell简易版
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
#include<pwd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
void Parse(char* input, char* output[])//用于将路径进行切分,最后一个/后面为当前路径
{
char* p = strtok(input,"/");
int i = 0;
while(p!= NULL)
{
output[i++] = p;
p=strtok(NULL,"/");
}
}
void ParseArg(char* input, char* output[]) //用于对输入的命令进行切分
{
char* p = strtok(input," ");
int output_size = 0;
while(p != NULL)
{
output[output_size++] = p;
p = strtok(NULL," ");
}
output[output_size] = NULL;
}
void Exec(char* argv[]) //创建子进程执行进程程序替换
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
execvp(argv[0],argv);
//execvp失败会执行下面的语句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
int main()
{
while(1)
{
//首先显示shell提示
//通过getuid获得用户的id,然后通过getpwuid通过用户的id查找用户的passwd数据来获取系统登录的用户名
//获得当前路径
char path[100] = {0};
getcwd(path,sizeof(path)); //getcwd获得的是绝对路径(path里面存放的是绝对路径)
char* path2[100] = {0};
Parse(path,path2);
char* path3 = NULL; //path里面为绝对路径,将path按照/进行分割,分割的结果保存在path2里面,path3里面保存最后一个字符串,即相对路>径
int i = 0;
while(path2[i] !=NULL)
{
path3= path2[i];
i++;
}
struct passwd *pwd;
pwd = getpwuid(getuid());
//获得主机名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname));
printf("[%s@%s %s MyShell]",pwd->pw_name,hostname,path3);
fflush(stdout);
//从标准输入读取字符串
char buf[1024] = {0};
gets(buf);
//解析字符串,解析出指令和参数
char* argv[100] = {};
ParseArg(buf,argv);
//子进程执行exec函数,将这个过程封装在Exec函数里
Exec(argv);
}
return 0;
}
运行结果:(注意到自己编写的shell的命令提示符后面会带上MyShell,这是为了与自己的shell进行区分)
让Shell支持管道
管道的实现方法
1.管道”|”是将”|”左边指令的执行结果作为”|”右边的输入。
2.我们可以将一条带有”|”的指令进行分割,将管道左边指令的输出结果重定向到管道的输入(也就是管道的写端),将标准输入重定向至管道的读端。让两个进程分别执行两边重定向后的指令。
管道的具体实现
(1)首先判断一条指令是否包含管道(指令已经存放至char*的数组里面,且该数组以NULL作为结束标志)
int IsPipe(char* argv[])
{
int i = 0;
while(argv[i] != NULL)
{
if(strcmp(argv[i], "|") == 0)
{
return i+1; //i的位置是管道,则i+1就是管道的下一个命令
}
++i;
}
return 0;
}
(2)将管道左右两边的指令进行切分,切分后的分别放入两个char*数组里,并保证每个数组以NULL作为结束标志
void ParsePipe(char* input[], char* output1[],char* output2[])//用于将input按照|进行切分,最后一个后面为当前路径
{
int i = 0;
int size1 = 0;
int size2 = 0;
int ret = IsPipe(input); //ret是input数组中管道"|"的下一个位置
while(strcmp(input[i], "|")!=0)
{
output1[size1++] = input[i++];
}
output1[size1] = NULL; //将分割出来的两个char*数组都以NULL结尾
int j = ret;//j指向管道的后面那个字符
while(input[j] != NULL)
{
output2[size2++] = input[j++];
}
output2[size2] = NULL;
}
(3)执行包含管道的指令(利用execvp函数,首先创建一个进程,让子进程创建一个管道,再创让子进程建一个子进程(孙子进程),让孙子进程执行管道左边的指令,并将输出结果重定向入管道,让子进程从执行管道右边的指令)
void ExecvPipe(char* argv1[],char* argv2[]) //argv1为左边指令及参数的数组argv2则为存放管道右边指令及参数的数组
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child,创建一个管道
int fd[2];
int ret = pipe(fd);
if(fork() == 0 )
{
//子进程创建一个子进程,用于执行管道之前的命令
close(fd[0]); //孙子进程关闭掉读端
dup2(fd[1],1); //将标准输出作为管道的标准输入
execvp(argv1[0],argv1);
}
wait(NULL);
close(fd[1]); //子进程关闭掉写端
dup2(fd[0],0); //将标准输入重定向至管道的读端
execvp(argv2[0],argv2);
//execvp失败会执行下面的语句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
(4)整体代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
#include<pwd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
//用于将路径进行切分,最后一个/后面为当前路径
void Parse(char* input, char* output[])
{
char* p = strtok(input,"/");
int i = 0;
while(p!= NULL)
{
output[i++] = p;
p=strtok(NULL,"/");
}
}
//用于对输入的命令进行切分
void ParseArg(char* input, char* output[])
{
char* p = strtok(input," ");
int output_size = 0;
while(p != NULL)
{
output[output_size++] = p;
p = strtok(NULL," ");
}
output[output_size] = NULL;
}
void Exec(char* argv[]) //创建子进程执行进程程序替换
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child
execvp(argv[0],argv);
//execvp失败会执行下面的语句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
//判断指令是否包含管道
int IsPipe(char* argv[])
{
int i = 0;
while(argv[i] != NULL)
{
if(strcmp(argv[i], "|") == 0)
{
return i+1; //i是管道,则i+1就是管道的下一个命令
}
++i;
}
return 0;
}
void ParsePipe(char* input[], char* output1[],char* output2[])//用于将input按照|进行切分,最后一个后面为当前路径
{
int i = 0;
int size1 = 0;
int size2 = 0;
int ret = IsPipe(input);
while(strcmp(input[i], "|")!=0)
{
output1[size1++] = input[i++];
}
output1[size1] = NULL;
int j = ret;//j指向管道的后面那个字符
while(input[j] != NULL)
{
output2[size2++] = input[j++];
}
output2[size2] = NULL;
}
void ExecvPipe(char* argv1[],char* argv2[])
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child,创建一个管道
int fd[2];
int ret = pipe(fd);
if(fork() == 0 )
{
//子进程创建一个子进程,用于执行管道之前的命令
close(fd[0]); //孙子进程关闭掉读端
dup2(fd[1],1); //将标准输出作为管道的标准输入
execvp(argv1[0],argv1);
}
wait(NULL);
close(fd[1]); //关闭掉写端
dup2(fd[0],0);
execvp(argv2[0],argv2);
//execvp失败会执行下面的语句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
int main()
{
//获得当前路径
while(1)
{
//首先显示shell提示
//通过getuid获得用户的id,然后通过getpwuid通过用户的id查找用户的passwd数据来获取系统登录的用户名
//获得当前路径
char path[100] = {0};
getcwd(path,sizeof(path)); //getcwd获得的是绝对路径(path里面存放的是绝对路径)
char* path2[100] = {0};
Parse(path,path2);
char* path3 = NULL; //path里面为绝对路径,将path按照/进行分割,分割的结果保存在path2里面,path3里面保存最后一个字符串,即相对路>径
int i = 0;
while(path2[i] !=NULL)
{
path3= path2[i];
i++;
}
struct passwd *pwd;
pwd = getpwuid(getuid());
//获得主机名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname));
printf("[%s@%s %s MyShell]",pwd->pw_name,hostname,path3);
fflush(stdout);
//从标准输入读取字符串
char buf[1024] = {0};
//解析字符串,解析出指令和参数
char* argv[100];
ParseArg(buf,argv);
//判断解析出来的字符串数组里面是否含有管道
int ret1 = IsPipe(argv);
if(ret1 > 0)
{
//是管道
char* argv1[10];
char* argv2[10];
ParsePipe(argv,argv1,argv2);
ExecvPipe(argv1,argv2);
}
else
{
Exec(argv);
}
}
return 0;
更多推荐
所有评论(0)