1. 程式人生 > >Linux(小專案)————shell的實現,包含重定向、內建命令。

Linux(小專案)————shell的實現,包含重定向、內建命令。

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("[%[email protected]",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();
		
	}
}