Linux(小项目)————shell的实现,包含重定向、内建命令。

版权声明:本文为博主原创文章,未经博主允许不得转载。 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();
		
	}
}

猜你喜欢

转载自blog.csdn.net/FangXiaXin/article/details/83271603