linuxc 實現my_shell

問題

1.cd命令的實現(cd ,cd ~,cd -,cd path)

由於cd命令是shell 內建的命令,是不能調用系統的命令實現cd,所以就得手寫

所用函數:

char * getcwd(char * buf, size_t size); //取得當前工作目錄
buf 爲所取得的路徑,   size爲所取的大小
int chdir(const char * path); //改變當前目錄
path 爲要切換的路徑

因爲要實現 cd -,所以要將每次的路徑保存下來,(這裏用cd_pathnametemp)保存

                getcwd(cd_pathname,100);
                if((argcount == 1)  || strcmp(arg[1],"~") == 0)  
                {   
                        strcpy(cd_pathnametemp,cd_pathname);
                        my_chdir();   //更改家目錄
                }   
                else if(strcmp(arg[1],"-") == 0)
                {   
                //      strcpy(cd_pathnametemp,cd_pathname);
                        chdir(cd_pathnametemp);
                        strcpy(cd_pathnametemp,cd_pathname);
                }   
                else
                {   
                        strcpy(cd_pathnametemp,cd_pathname);
                        chdir(arg[1]);    //更改當前工作目錄
                }   
                
2.ls輸出加顏色

因爲ls 本身是不輸出顏色的,而我們的shell 中能輸出顏色是因爲 用了別名

 alias ls='ls --color=auto'

所以在處理 ls 的時候,給它加上 - -color=auto參數就ok

3.屏蔽ctrl + c

一個優秀的程序是不會輕易的掛掉,所以不屏蔽ctrl + c 的話,用戶一個ctrl + c 就會讓程序掛掉,那樣會有多尷尬.
而屏蔽ctrl + c也不難

 signal(SIGINT, SIG_IGN);

SIGINT :當用戶按下了<ctrl + c >時,用戶終端向正在運行中的由該終端啓動的程序發出此信號,默認爲終止進程.而進程終止後,程序自然也就被終止了
SIG_IGN : 忽略該信號

4.命令自動補全

瞭解了之後,發現readline 這個庫是真的強大,有許多特別好用,方便的函數,下面的history也是這個庫裏的函數

自動補全這個功能需要用Readline 庫 ,沒有的話需要安裝

			sudo apt-get install libreadline6-dev 

因爲readline 是動態鏈接庫 所以 ,編譯的時候 需要加上 -lreadline
要加頭文件

#include<readline/readline.h>

        char * str = readline(" ");
5.通過上下查找歷史命令

頭文件

#include<readline/history.h>

同樣,編譯的時候需要加上 -lreadline

        add_history(str);

每次將所讀取的命令加到 add_history()中就ok了.

對readline還不是很熟悉的可以做個小測試熟悉一下

6.將my_shell 設置環境變量,使程序可以像bash,zsh這種shell一樣運行;

這個問題就將my_shell 的可執行程序 加在 /bin 下就搞定了.

以上就是這次shell 的主要問題.
其他問題
    1. 支持輸入輸出重定向(< > >>)
    2. 支持管道(|)
    3. 支持後臺運行程序(&)
看源碼就可以搞定,我就不再贅述.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<dirent.h>
#include<pwd.h>
#include<readline/readline.h>
#include<readline/history.h>


#define normal  0 //一般命令
#define out_redirect  1  //輸出重定向
#define in_redirect 2 //輸入重定向
#define have_pipe   3  //命令中有管道
#define add_out_redirect 4
#define add_in_redirect 5
#define CLOSE "\001\033[0m\002"                 // 關閉所有屬性
char cd_pathnametemp[PATH_MAX] = "/home/tt";    //默認家目錄
char cd_pathname[PATH_MAX];


void print_prompt();   //打印提示符  
void get_input(char *);   //得到輸入的命令
void explain_input(char *,int *,char a[ ][256]);  //對輸入命令進行解析
void do_cmd(int ,char a[ ][256]);  //執行命令
int find_command(char *);    //查找命令中的可執行程序
void my_dir();//cd 到家目錄
int main(int argc,char **argv)
{
	 signal(SIGINT, SIG_IGN);
	int i;
	int argcount = 0; //記錄 命令的個數
	char arglist[100][256];   //存儲命令
	char **arg = NULL;
	char *buf = NULL;

	buf = (char *)malloc(256);
	if(buf == NULL)
	{
		perror("malloc failed\n");
		exit(-1);
	}


	while(1)
	{
		memset(buf,0,sizeof((buf)));
		print_prompt();   //輸出命令提示符
		get_input(buf);   //獲取輸入
		//若輸入的命令爲 exit 或 logout 則退出本程序
		if(strcmp("exit\n",buf) == 0 || strcmp("logout\n",buf) == 0)  break;
		if(strcmp(buf,"\n") == 0) continue;
		//清空 arglist 
		for(i = 0;i < 100;i++)    arglist[i][0] = '\0';
		argcount = 0;//命令個數 清0


		explain_input(buf,&argcount,arglist);

		do_cmd(argcount,arglist);

	}
	if(buf == NULL)
	{
		free(buf);
		buf = NULL;
	}
	exit(0);

}
void my_chdir()
{
/*	int i,flag = 0,j;
	char name[30];
	char pathname[100];
	char pathnametemp[100];
	int uid;
	struct passwd *data;
	//uid_t uid;
	uid = getuid();  //獲取uid 
	data = getpwuid(uid);
	getcwd(pathname,100);
	//處理路徑  
	int len = strlen(pathname);
	for(i = 0;i < len;i++)
	{
		if(pathname[i] == '/') flag++;
		if(flag == 3)  break;
	}
	for(j = 0;j < i;j++)
	{
		pathnametemp[j] = pathname[j];
	}

	pathnametemp[j] = '\0';
*/
	//printf( "%s\n",pathnametemp);
	//chdir(pathnametemp);
	chdir("/home/tt");
}

//輸出 命令提示符
void print_prompt()
{
	int i,flag = 0,j;
	char name[30];
	char pathname[100];
	char pathnametemp[100];
	int uid;
	struct passwd *data;

	//uid_t uid;
	uid = getuid();
	data = getpwuid(uid);
	printf("\033[43;35m%s@\033[0m",data->pw_name);
	gethostname(name,30);
	printf( "\033[43;35m%s:\033[0m",name);
	getcwd(pathname,100);
	
	if(pathname[1] != 'h' && pathname[2] != 'o')
	{
		printf( "\033[35;43m%s\033[0m",pathname);
		return ;
	}

	//處理路徑  
	int len = strlen(pathname);
	for(i = 0;i < len;i++)
	{
		if(pathname[i] == '/') flag++;
		if(flag == 3)  break;
	}
	for(j = i;j < len;j++)
	{
		pathnametemp[j-i] = pathname[j];
	}
	pathnametemp[len-i] = '\0';
	strcpy(pathname,"~");
	strcat(pathname,pathnametemp);
	printf( "\033[35;43m%s\033[0m",pathname);

	//打印用戶提示符
	if(0 == uid)  printf( "\033[40;32m#\033[0m");
	else printf( "\033[40;32m$\033[0m");
	
	return ;
}

//獲取用戶輸入  
void get_input(char *buf)
{
	int len = 0;
	int ch;

	char * str = readline(" "CLOSE);
	add_history(str);
	strcpy(buf,str);
	buf[strlen(buf)] = '\n';


	/*free(str);
	ch = getchar();
	while(len < 256 && ch != '\n')
	{
		//char * str = readline("");
		//free(str);
		buf[len++] = ch;
		ch = getchar( );
	}

	if(len == 256)
	{
		printf( "command is too long \n");
		exit(-1);  //若輸入的命令太長,則退出程序
	}

	buf[len] = '\n';
	len++;
	buf[len] = '\0';
	*/

}

//解析buf 中的命令,將結果存入 arglist 中,命令以回車符號'\n'結束
//如 輸入命令"ls -l /temp" 則arglist[0],arglist[1],arglist[2],分別爲 ls,-l,/temp

void explain_input(char *buf,int *argcount,char arglist[100][256])
{
	char *p = buf;
	char *q = buf;
	int number = 0;

	while(1)
	{
		if(p[0] == '\n')  break;
		if(p[0] == ' ') p++;
		else
		{
			q = p;
			number = 0;  //記錄每個命令的長度

			while(q[0] != ' ' && (q[0] != '\n'))
			{
				number++;
				q++;
			}

			strncpy(arglist[*argcount],p,number+1);  //*argcount 爲命令的個數
			arglist[*argcount][number] = '\0';
			*argcount = *argcount + 1;
			p = q;
		}
	}
}

//執行arglist 中存放的命令 ,argcount 爲待執行命令的參數個數  
void do_cmd(int argcount,char arglist[100][256])
{
	int flag = 0;
	int how = 0;   //用於只是命令中是否含有 < , > , | 
	int background = 0; //表示命令中是否有後臺運行表示符
	int status;
	int i;
	int fd;
	char *arg[argcount + 1];
	char *argnext[argcount + 1];
	char *file;  //保存文件名
	pid_t pid;
	int cdflag = 0;

	//將命令取出 
	for(i = 0;i < argcount;i++)   arg[i] = (char *)arglist[i];
	//給ls 加顏色
	if(strcmp(arg[0],"ls") == 0)
	{
		arg[argcount] = "--color=auto";
		arg[argcount + 1] = NULL;
	}
	else 	arg[argcount] = NULL;


	//cd   
	if(strcmp(arg[0],"cd") == 0)
	{
		getcwd(cd_pathname,100);
		if((argcount == 1)  || strcmp(arg[1],"~") == 0)  
		{
			strcpy(cd_pathnametemp,cd_pathname);
			my_chdir();   //更改家目錄
		}
		else if(strcmp(arg[1],"-") == 0)
		{
		//	strcpy(cd_pathnametemp,cd_pathname);
			chdir(cd_pathnametemp);
			strcpy(cd_pathnametemp,cd_pathname);
		}
		else
		{
			strcpy(cd_pathnametemp,cd_pathname);
			chdir(arg[1]);    //更改當前工作目錄
		}


		return ;
	}

	//查看命令行是否有後臺運行符
	for(i = 0;i < argcount; i++)
	{
		if(strncmp(arg[i],"&",1) == 0)
		{
			if(i == argcount -1)
			{
				background = 1;
				arg[argcount - 1] = NULL;
				break;
			}
			else
			{
				printf( "wrong command\n");
				return ;
			}
		}
	}

	//查看命令行 是否 有 重定向 和管道符
	for(i = 0; arg[i] != NULL;i++)
	{
		if(strcmp(arg[i],">") == 0)
		{
			flag++;
			how = out_redirect;
			if(arg[i+1] == NULL) flag++;
		}
		if(strcmp(arg[i],"<") == 0)
		{
			flag++;
			how = in_redirect;
			if(i == 0) flag++;
		}
		if(strcmp(arg[i],"|") == 0)
		{
			flag++;
			how = have_pipe;
			if(arg[i+1] == NULL) flag++;
			if(i == 0)  flag++;
		}
		if(strcmp(arg[i],">>") == 0)
		{
			flag++;
			how = add_out_redirect;
			if(arg[i+1] == NULL)  flag++;
		}
		if(strcmp(arg[i],"<<") == 0)
		{
			flag++;
			how = add_in_redirect;
			if(i == 0) flag++;
		}

	}
	//若 flag == 1, 則有
	//若 flag > 1,則格式錯誤,不支持
	if(flag > 1)
	{
		printf( "wrong command\n");
		return ;
	}

	//命令中 只含一個輸出替換重定向符號
	if(how == out_redirect)
	{
		for(i = 0;arg[i] != NULL;i++)
		{
			if(strcmp(arg[i],">") == 0)
			{
				file = arg[i+1];
				arg[i] = NULL;
			}
		}
	}

	//命令中只含有 一個輸入替換重定向	
	if(how == in_redirect)
	{
		for(i = 0;arg[i] != NULL;i++)
		{
			if(strcmp(arg[i],"<") == 0)
			{
				file = arg[i+1];
				arg[i] = NULL;
			}
		}
	}
	//命令中只含有 輸出追加重定向
	if(how == add_out_redirect)
	{
		for(i = 0;arg[i] != NULL;i++)
		{
			if(strcmp(arg[i],">>") == 0)
			{
				file = arg[i+1];
				arg[i] = NULL;
				arg[i] = NULL;
			}
		}
	}

	//命令中只含有 輸入追加重定向
	if(how == add_in_redirect)
	{
		for(i = 0;arg[i] != NULL;i++)
		{
			if(strcmp(arg[i],"<<") == 0)
			{
				file = arg[i+1];
				arg[i] = NULL;
			}
		}
	}
	//命令中只含有 一管道符號
	//把管道符號後面的部分存入argnext中,管道後面的部分是一個可執行 的shell 命令
	if(how == have_pipe)
	{
		for(i = 0;arg[i] != NULL;i++)
		{
			if(strcmp(arg[i],"|") == 0)
			{
				arg[i] = NULL;
				int j;
				//將| 後面的 命令存入argnext 中
				//j - i -1   就是 從零開始,往後加
				for(j = i+1;arg[j] != NULL;j++)  argnext[j-i-1] = arg[j];
				argnext[j-i-1] = arg[j];
				break;
			}
		}
	}

	if((pid = fork()) < 0)
	{
		printf( "fork error\n");
		return ;
	}

 	    //0  一般命令
	    //1  輸出重定向
	    //2  輸入重定向
	    //3  命令中有管道
	//pid 爲 0 說明是子進程 ,在子進程中執行命令
	switch(how)
	{
		case 0: //一般命令
			if(pid == 0)
			{
				if(!(find_command(arg[0])))
				{
					printf( "%s : command not found\n",arg[0]);
					exit(0);
				}

				execvp(arg[0],arg);
				exit(0);
			}
			break;
		case 1:  //替換輸出重定向
			if(pid == 0)
			{
				if(!(find_command(arg[0])))
				{
					printf( "%s : command not found\n",arg[0]);
					exit(0);
				}
				fd = open(file,O_RDWR | O_CREAT | O_TRUNC,0644);  //可讀可寫 ,若文件不存在就自動建立該文件  ,將文件長度清 0
				dup2(fd,1);  //賦值 文件描述符描述符   將本來 的文件描述符 改爲 1 標準輸入
				execvp(arg[0],arg);
				exit(0);
			}
			break;
		case 2:  //替換輸入重定向
			if(pid == 0)
			{
				if(!(find_command(arg[0])))
				{
					printf( "%s : command not found\n",arg[0]);
					exit(0);
				}
				fd = open(file,O_RDONLY);
				dup2(fd,0);
				execvp(arg[0],arg);
				exit(0);
			}
			break;
		case 3:  //含有管道
			if(pid == 0)
			{
				int pid2;
				int status2;
				int fd2;

				if((pid2 = fork()) < 0)
				{
					printf( "fork2 error\n");
					return ;
				}
				else if(pid2 == 0)
				{
					if(!(find_command(arg[0])))
					{
						printf( "%s : command not found\n",arg[0]);
						exit(0);
					}

					fd2 = open("/tmp/youdonotknowfile",O_WRONLY | O_CREAT | O_TRUNC,0644);
					dup2(fd2,1);
					execvp(arg[0],arg);
					exit(0);
				}
				if(waitpid(pid2,&status2,0) == -1)  printf( "wait for child procrss error\n");

				if(!(find_command(argnext[0])))
				{
					printf( "%s : command not found\n",argnext[0]);
					exit(0);
				}

				fd2 = open("/tmp/youdonotknowfile",O_RDONLY);
				dup2(fd2,0);
				execvp(argnext[0],argnext);

				if(remove( "/tmp/youdonotknowfile"))  printf( "remove error\n");
				exit(0);

			}
			break;
		case 4://追加輸出重定向
			if(pid == 0)
			{
				if(!(find_command(arg[0])))
				{
					printf( "%s : command not found\n",arg[0]);
					exit(0);
				}

				fd = open(file,O_RDWR | O_CREAT | O_APPEND);
				dup2(fd,1);
				execvp(arg[0],arg);
				exit(0);
			} break;
		default : break;
	}

	//若命令中有 &,表示後臺運行,父進程直接返回,不等待子進程結束
	if(background == 1)
	{
		printf( "[process id %d ]\n",pid);
		return ;
	}

	//父進程等待子進程結束
	if(waitpid(pid,&status,0) == -1)  printf( "wait for child process error\n");

}

//查找命令中的可執行文件
int find_command(char *command)
{
	DIR* dp;
	struct dirent * dirp;
	char * path[] = {"./","/bin","/usr/bin",NULL};

	//使用當前目錄下的程序可以運行,如命令"./fork "可以被正確解釋和執行 
	if(strncmp(command,"./",2) == 0)   command = command + 2;

	//分別在當前目錄 /bin ,/usr/bin 目錄查找要執行的程序
	//
	
	int i = 0;
	while(path[i] != NULL)
	{
		if((dp = opendir(path[i])) == NULL)   printf( "can not open /bin\n");

		while((dirp = readdir(dp)) != NULL)
		{
			if(strcmp(dirp->d_name,command) == 0)
			{
				closedir(dp);
				return 1;
			}
		}

		closedir(dp);
		i++;
	}

	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章