自定义Shell实现

设计自定义Shell程序

一、初始Shell程序

  #include <stdio.h>
  #include <unistd.h>
  #include <string.h>
  #include <sys/wait.h>
  #include <sys/types.h>
  
  int main() {
      /* 输入的命令行 */
      char cmd[256];
      /* 命令行拆解成的各部分,以空指针结尾 */
      char *args[128];
      while (1) {
          /* 提示符 */
          printf("# ");
          fflush(stdin);
          fgets(cmd, 256, stdin);
          /* 清理结尾的换行符 */
          int i;
          for (i = 0; cmd[i] != '\n'; i++)
              ;
          cmd[i] = '\0';
          /* 拆解命令行 */
          args[0] = cmd;
          for (i = 0; *args[i]; i++)
              for (args[i+1] = args[i] + 1; *args[i+1]; args[i+1]++)
                  if (*args[i+1] == ' ') {
                      *args[i+1] = '\0';
                     	args[i+1]++;
                      break;
                  }
          args[i] = NULL;
  
          /* 没有输入命令 */
          if (!args[0])
              continue;
  
          /* 内建命令 */
          if (strcmp(args[0], "cd") == 0) {
              if (args[1])
                  chdir(args[1]);
              continue;
          }
          if (strcmp(args[0], "pwd") == 0) {
              char wd[4096];
              puts(getcwd(wd, 4096));
              continue;
          }
          if (strcmp(args[0], "exit") == 0)
              return 0;
  
          /* 外部命令 */
          pid_t pid = fork();
          if (pid == 0) {
              /* 子进程 */
              execvp(args[0], args);
              /* execvp失败 */
              return 255;
          }
          /* 父进程 */
          wait(NULL);
      }
 }
代码功能分析:
  • line 9-31 :从命令行输入流将字符串读入数组中并拆分为命令部分args[0]和参数部分args[1]
  • line 38-49:内部命令的判断和执行:
    对args[0]进行匹配,若为内建命令,则执行系统调用实现相关功能。
    内部命令:直接在shell中实现,例如cd, pwd, exit等简单的系统命令。Shell启动时,内部命令也被加载进内存中,因此执行速度更快。执行内部命令无需创建子进程。
  • line 52-60:外部命令的执行:
    创建子进程并用于执行外部命令。
    外部命令:通常是一些实用程序,其包含的程序量较大,故不会随shell加载,只有当调用这些命令时才装载进内存中。外部命令需存放在环境变量目录中,如/bin,/sbin等。当使用外部命令时,shell会fork一个子进程并调用execvp找到这个外部命令并执行。

Q:为什么cd命令必须做成shell程序的功能,而不能编译为外部程序,通过调用来运行呢?
A: 如果cd作为外部程序执行,则必须fork一个子进程来执行,而这个子进程运行结束后,回到父进程,父进程的环境目录并未发生改变。故cd无法通过外部命令来实现。

二、改进的Shell程序

改进后的shell程序将拆分命令模块保留在main()函数中,而执行命令模块封装在函数execute()中以便递归调用。递归的思想有利于多重管道、复杂命令的实现。

健壮性:
  • 无输入:判断args[0],若为换行符,则不执行、进入下一次循环
  • cd:判断拆分的args[1]之后的每个一维数组,有空格则忽略;如果该目录下没有匹配到的子目录,输出“ERROR!”
  • fork():如果没有创建子进程(即pid < 0),则输出"ERROR!"后强制退出
  • 括号匹配:如果没有找到相应的反括号")",输出"Match ERROR!"后退出
  • echo输出环境变量:若环境变量表中未匹配到参数,输出"NOT FOUND!"后退出
  • exec():如果该函数没有自行退出进程,说明调用失败,退出并返回-1
支持管道:

调用pipe函数创建管道并分别与标准输入输出端口相连,利用dup()、dup2()函数进行参数的传递拷贝。

支持重定向:

调用open函数设置文件指针,并同样使用dup()、dup2()函数进行参数拷贝。

支持修改环境变量:

在内建命令中添加对字符串"export"的匹配,然后使用系统调用setenv()修改环境变量。

支持echo:

同样在内建命令中添加对字符串"echo"的匹配,然后使用系统调用获取变量并输出即可。

支持alias:

定义一个全局的alias_array数组(下文记为数组a),规定映射关系 f : a[i] = a[i+100]。
每次识别到alias stringA=“stringB” 命令后,将stringA存在a[i]中,stringB存在a[i+100]中。
每次进入循环,在数组a[0]~a[99]中匹配字符串args[0]。如果匹配成功a[k],则将a[k+100]作为cmd执行。

支持后台运行:(sleep 3 ; echo USTC_CS) &

从外到内处理 &、()、; 每一步都将命令拆分为子命令后作为参数送进execute()递归即可。

源代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

char alias_array[200][256] ;
int alias_top = 0 ;
int main() {
	int execute (char *args[]) ;
    /* 输入的命令行 */
    char cmd[256];
    /* 命令行拆解成的各部分,以空指针结尾 */
    char *args[128];
    while (1) {
        /* 提示符 */
        printf("# ");
        fflush(stdin);
        fgets(cmd, 256, stdin);

		if(cmd[0] == '\n')
			continue ;

        /* 清理结尾的换行符 */
        int i;
        for (i = 0; cmd[i] != '\n'; i++)
            ;
        cmd[i] = '\0';
        /* 拆解命令行 */
        args[0] = cmd;
        for (i = 0; *args[i]; i++)
            for (args[i+1] = args[i] + 1; *args[i+1]; args[i+1]++)
                if (*args[i+1] == ' ') {
                    *args[i+1] = '\0';
                    args[i+1]++;
                    break;
                }
        args[i] = NULL;
		//for (int i = 0 ; strcmp(args[i] , "\0") != 0 ; i ++)
		//	puts(args[i]) ;		
		if (strcmp(args[0] , "exit") == 0)
			return 0 ;
    	execute(args) ;
	}
}

int execute(char *args[])
{
	int i ;
	
	/* & */
	for (i = 0 ; args[i] != 0 && strcmp(args[i] , "&") != 0 ; i++) ;
	if (args[i] != 0)
	{
		args[i] = NULL ;
		pid_t pid = fork() ;
		if (pid < 0)
		{
			puts("ERROR!") ;
			exit(-1) ;
		}
		if (pid == 0)
		{
			execute(args) ;
			exit(0) ;
		}
		return 0 ;
	}

	/* 括号 */
	for (i = 0 ; args[i] != 0 && args[i][0] != '(' ; i++) ;
	if (args[i] != 0)
	{
		int j , k;
		for (k = 0 ; args[i][k] != '\0' ; k++)
			args[i][k] = args[i][k+1] ;
		for(j = i ; args[j] != NULL ; j++)
			for(k = 0 ; args[j][k] != '\0' ; k ++)
				if (args[j][k] == ')')
				{
					args[j][k] = '\0' ;
					args[j+1] = NULL ;
					execute(args + i) ;
					return 0 ;
				}
		puts("Match ERROR!") ;
		return -1 ;
	}

	/* 分号 */
	for (i = 0 ; args[i] != 0 && args[i][0] != ';' ; i ++) ; 
	if (args[i] != 0)
	{
		args[i] = NULL ;
		execute(args) ;
		execute(args+i+1) ;
		return 0 ;
	}

	/* 管道 */
	for (i = 0 ; args[i] != 0 && strcmp(args[i] , "|") != 0 ; i ++) ;
	if (args[i] != 0)   // 未到末尾,存在管道
	{
		int filedes[2] ;
		pipe(filedes) ;
		pid_t pid = fork() ;
		if (pid < 0)
		{
			puts("Error!") ;
			exit(-1) ;
		}
		if (pid == 0)   // 子进程
		{
			close(filedes[0]) ;       // 关闭读入端
			dup2(filedes[1] , 1) ;	// stdout标准输出拷贝至管道写入端
			args[i] = NULL;
			execute(args) ;
			close(filedes[1]) ;		// 递归执行
			exit(0) ;
		}
		// 父进程
		close(filedes[1]) ;
		int buffer = dup(0) ;		// 备份
		dup2(filedes[0] , 0) ;		// 管道读取端拷贝至标准输入
		execute(args+i+1) ;
		close(filedes[0]) ;			
		dup2(buffer , 0) ;
		waitpid(pid , NULL , 0) ;
		return 0 ;
	}

	/* 重定向 */
	for (i = 0 ; args[i] != 0 && (strcmp(args[i] , ">") != 0 && strcmp(args[i] , ">>") != 0) ; i ++) ;
	if (args[i] != 0)
	{
		int flag = 0 ;		// 0为覆盖,1为追加
		if (strcmp(args[i],">>") == 0)
			flag = 1 ;
		int buffer = dup(1) ;
		int filedes ;
		if (flag == 0)	// >
			filedes = open(args[i+1] , O_WRONLY|O_CREAT|O_TRUNC) ;
		else			// >>
			filedes = open(args[i+1] , O_WRONLY|O_CREAT|O_APPEND) ;
		for (i ; args[i+2] != NULL ; i++)
			args[i] = args[i+2] ;
		args[i] = NULL ;
		dup2(filedes , 1) ;
		execute(args) ;
		close(filedes) ;
		dup2(buffer , 1) ;
		return 0 ;
	}
	// <
	for (i = 0 ; args[i] != 0 && strcmp(args[i] , "<") != 0 ; i++) ;
	if (args[i] != 0)
	{
		int buffer = dup(0) ;
		int filedes ;
		filedes = open(args[i+1] , O_RDONLY) ;
		for (i ; args[i+2] != NULL ; i++)
			args[i] = args[i+2] ;
		args[i] = NULL ;
		dup2(filedes , 0) ;
		execute(args) ;
		close(filedes) ;
		dup2(buffer , 0) ;
		return 0 ;
	}
	
	// alias判断	
	for (i = 0 ; i < 100 ; i ++)
	{
		if (strcmp(args[0], alias_array[i]) == 0)
		{
			pid_t pid = fork() ;
			if (pid < 0)
			{
				puts("ERROR!") ;
				exit(-1) ;
			}
			if (pid == 0)
			{
				// 拆解新的命令
				char cmd[256] ;
				char *args[128] ;
				strcpy(cmd , alias_array[i+100]) ;
				int j ;
				for (j = 0 ; cmd[j] != '\0' && cmd[j] != '\n' ; j++) ;
				if (cmd[j] == '\n')
					cmd[j] == '\0' ;
				args[0] = cmd ;
				for (j = 0 ; *args[j] ; j++)
					for (args[j+1] = args[j] + 1; *args[j+1]; args[j+1]++)
						if (*args[i+1] == ' ')
						{
							*args[i+1] = '\0' ;
							args[i+1] ++ ;
							break ;
						}
				args[j] = NULL ;
				// 用新的命令递归
				execute(args) ;
				return 0 ;
			}
			wait(NULL) ;
			return 0 ;
		}
	}

	// 无输入
	if (args[0] == NULL)
		return 0 ;

	/* 内建命令 */
	if (strcmp(args[0], "cd") == 0) {
		int i ;
		//for (i = 1 ; args[i] != NULL ; i++)
		//	puts(args[i]) ;
		
		for (i = 1 ; strcmp(args[i] , " ") == 0 || strcmp(args[i] , "\0") == 0 ; i++) ;
		if (args[i])
		{
			if (args[i][0] == ' ')
				for (int k = 0 ; args[i][k] != '\0' ; k ++)
					args[i][k] = args[i][k+1] ;
			int error ;
			error = chdir(args[i]);
			if (error == -1)
			{
				puts("ERROR!") ;
				return 0 ;
			}
		}
		return 0 ;
	}

	if (strcmp(args[0], "pwd") == 0) {
		char wd[4096];
		puts(getcwd(wd, 4096));
		return 0 ;
	}

	if (strcmp(args[0], "exit") == 0)
		exit(0) ;

	/* 修改环境变量 */
	if (strcmp(args[0], "export") == 0)
	{
		int i ;
		for (i = 0 ; args[1][i] != '=' && args[1][i] != '\0' ; i ++) ;
		if (args[1][i] == '\0')
			return 0;
		args[1][i] = '\0' ;
		setenv(args[1] , args[1]+i+1 , 1) ;
		return 0;
	}

	/* 附加功能 */
	//echo
	if (strcmp(args[0], "echo") == 0)
	{
		if (strcmp(args[1], "~") == 0)
		{
			char *pathvar ;
			pathvar = getenv("HOME") ;
			printf("%s\n" , pathvar) ;
			return 0 ;
		}
		if (args[1][0] == '$')
		{
			char name[256] ;
			strcpy(name , args[1]+1) ;
			char *pathvar ;
			pathvar = getenv(name) ;
			if (pathvar == NULL)
			{
				puts("NOT FOUND!") ;
				return 0 ;
			}
			printf("%s\n" , pathvar) ;
			return 0 ;
		}
		//else
		//	puts(args[1]) ;
	}

	// alias
	if (strcmp(args[0], "alias") == 0)
	{
		int j ;
		char name[256] ;
		char command[256] ;
		for (j = 0 ; args[1][j] != '=' ; j ++)
			name[j] = args[1][j] ;
		name[j] = '\0' ;
		strcpy(alias_array[alias_top] , name) ;
		j += 2 ;	// 跳过= 和 '
		int k ;
		for (k = j ; args[1][k] != '\'' ; k++)
			command[k-j] = args[1][k] ;
		command[k] = '\0' ;
		strcpy(alias_array[alias_top+100] , command) ;
		alias_top ++ ;
		return 0 ;
	}

	/* 外部命令 */
	pid_t pid = fork();
	if (pid == 0) {
		/* 子进程 */
		execvp(args[0], args);
		/* execvp失败 */
		return 255;
	}
	/* 父进程 */
	wait(NULL);
	return 0 ;        
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值