版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FangXiaXin/article/details/83271603
bash原理:
通过上面bash的原理我们可以,了解到shell的框架与流程:
1.等待用户输入命令。
2.解析用户输入的字符串。
3.创建子进程执行exec程序替换
4.父进程等待子进程退出。
循环执行1~4步骤,即可完成my_shell。
最简单版本的my_shell实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
char* argv[8];
int argc = 0;
//解析用户指令,字符串的切分。
void do_parse(char* buf)
{
int status = 0;
int i;
int flag = 0;
char* file = 0;
if(buf[0] == '\n')
{
printf("comind error!\n");
return;
}
for(argc=i=0;buf[i];i++)
{
if(!isspace(buf[i])&&status == 0)
{
argv[argc++] = buf+i;
status = 1;
}
else if(isspace(buf[i]))
{
status = 0;
buf[i] = 0;
}
}
argv[argc] = NULL;
}
//创建子进程,子进程执行exec函数。
void do_exec()
{
pid_t pid = fork();
if(pid<0)
{
perror("fork\n");
exit(1);
}
else if(pid == 0)
{
execvp(argv[0],argv);
perror("execvp!\n");
exit(1);
}
else
{
while(1)
{
if(pid == wait(NULL))
break;
}
}
}
int main()
{
//1.等待用户输入命令
char buf[1024] = {};
while(1)
{
printf("[#fangxia pro_ctrl]");
fflush(stdout);
size_t i = read(0,buf,sizeof(buf)-1);
buf[i-1] = '\0';
//2.解析用户
do_parse(buf);
//3.创建子进程,子进程函数替换,父进程程等待。
do_exec();
}
}
运行效果:
缺点:
1.不能获取登录名,不能获取主机名,不能获取当前目录。
2.不能操作重定向。
3.不能执行内建指令。
通过这个简单的shell我们可以在这个基础上扩展很多功能:
- 获取主机名:
使用到的函数接口:getuid()获取用户ID、getpwuid()获取用户信息、gethostname()获取主机信息、getcwd()获取当前目录路径。
//获取主机名
struct passwd* pwd;
//uid_t getuid(void) 获取登录id
//struct passwd* getpwuid(uid_t)获取登录信息
pwd = getpwuid(getuid());
//获取主机名
char name[100] = {0};
gethostname(name,sizeof(name)-1);
//获取当前目录
char cwd[100] = {0};
getcwd(cwd,sizeof(cwd)-1);
int len = strlen(cwd);
char* p = cwd + len;
while(*p != '/'&&len--)
{
p--;
}
p++;
- 重定向功能:
方法:
1.在解析出来的用户命令中查找重定向符">"。
2.根据重定向符后面的文件,来重定向文件描述符。
3.执行exec函数,退出子进程。
重定向原理:
在这里有一个函数可用来实现重定向:
oldfd文件描述符指向的内容改为newfd指向的内容(文件)。
//1.考虑重定向
int i = 0;
int flag = 0;
int cfd;
for(;argv[i];i++)
{
if(strcmp(">",argv[i]) == 0)
{
flag = 1;
break;
}
}
if(flag)
{
argv[i] = NULL;
close(1);
int fd = open(argv[i+1],O_CREAT|O_WRONLY|O_APPEND,0664);
//将标准输出重定向到一个文件中
cfd = dup2(1,fd);
}
execvp(argv[0],argv);
if(flag)//将标准输出重定向回1
{
close(1);
dup2(cfd,1);
}
exit(0);
- 内建命令的实现:
1.在解析好的字符串中查找,内建指令。
2.在fork之前直接调用函数接口实现内建指令。
3.退出子进程。
内建指令cd的实现:
if(strcmp("cd",argv[0]) == 0)
{
if(chdir(argv[1])<0)
{
perror("cd !\n");
exit(0);
}
}
通过这种方法可以实现很多功能,这里就不一 一列举了,后面有机会再更新。
my_shell小项目实例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<pwd.h>
#include<string.h>
char* argv[1024];
int argc = 0;
int flag = 0;
int cfd = -1;
void do_parse(char* buf)
{
int status = 0;
int i;
int fd = -1;
char* file = 0;
for(argc=i=0;buf[i];i++)
{
if(!isspace(buf[i])&&status == 0)
{
argv[argc++] = buf+i;
status = 1;
}
else if(isspace(buf[i]))
{
status = 0;
buf[i] = 0;
}
}
argv[argc] = NULL;
}
void do_exec()
{
//2.处理cd
if(strcmp("cd",argv[0]) == 0)
{
if(chdir(argv[1])<0)
{
perror("cd !\n");
exit(0);
}
}
pid_t pid = vfork();
if(pid<0)
{
perror("fork!\n");
exit(0);
}
else if(pid == 0)
{
//3.处理pwd指令
char name[1024] = {0};
if(strcmp("pwd",argv[0]) == 0)
{
if(getcwd(name,sizeof(name)-1)<0)
{
printf("getcwd error!\n");
exit(1);
}
}
//1.考虑重定向
int i = 0;
int flag = 0;
int cfd;
for(;argv[i];i++)
{
//a.查看有没有重定向
if(*argv[i]== '>')
{
flag = 1;
//b.判断是否为追加模式重定向
if(++argv[i] && *argv[i] == '>')
flag = 2;
break;
}
}
//当没有重定向时刷出pwd结果
if(flag == 0)
{
if(name[0] != '\0')
printf("%s\n",name);
}
//a.覆盖模式重定向
if(flag == 1)
{
argv[i] = NULL;
close(1);
int fd = open(argv[i+1],O_CREAT|O_WRONLY|O_TRUNC,0664);
//将标准输出重定向到一个文件中
cfd = dup2(1,fd);
}
//b.追加模式重定向
if(flag == 2)
{
argv[i] = NULL;
close(1);
int fd = open(argv[i+1],O_CREAT|O_WRONLY|O_APPEND,0664);
//将标准输出重定向到一个文件中
cfd = dup2(1,fd);
}
execvp(argv[0],argv);
if(flag)//将标准输出重定向回1
{
close(1);
dup2(cfd,1);
}
exit(0);
}
else
{
while(1)
{
if(pid == wait(NULL))
break;
}
}
}
int main()
{
//1.等待用户输入命令
while(1)
{
char buf[1024] = {};
struct passwd* pwd;
//uid_t getuid(void) 获取登录id
//struct passwd* getpwuid(uid_t)获取登录信息
pwd = getpwuid(getuid());
//获取主机名
char name[100] = {0};
gethostname(name,sizeof(name)-1);
//获取当前目录
char cwd[100] = {0};
getcwd(cwd,sizeof(cwd)-1);
int len = strlen(cwd);
char* p = cwd + len;
while(*p != '/'&&len--)
{
p--;
}
p++;
printf("[%s@",pwd->pw_name);
printf("%s",name);
printf(" %s]$",p);
fflush(stdout);
size_t i = read(0,buf,sizeof(buf)-1);
buf[i-0] = '\0';
//2.解析用户
do_parse(buf);
//3.创建子进程,子进程函数替换,父进程程等待。
do_exec();
}
}