设计简单的命令行 myshell

使用方法

  1. 在Ubuntu下创建一个myshell.c文件 touch myshell.c
  2. 复制代码到myshell.c文件中
  3. 编译c文件 gcc -o myshell myshell.c
  4. 执行文件 ./myshell
  5. 输入shell支持的有效命令
  6. quit或exit退出shell

支持功能

1.支持任意命令的I/O重定向
2.支持任意命令间的管道符操作
3.支持在/bin和/usr/bin里的所有可执行命令
比如:常见的目录和文件操作,比如新建(/删除/复制/显示)目录或文件

实现代码

#include <stdio.h>
#include <stdlib.h>     
#include <string.h>
#include <fcntl.h>      
#include <dirent.h>     
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <pwd.h>
//颜色宏定义
#define NONE                 "\e[0m"
#define L_RED                "\e[1;31m"
#define L_GREEN              "\e[1;32m"
#define BLUE                 "\e[0;34m"
#define WHITE                "\e[1;37m"
#define L_PURPLE             "\e[1;35m"
//分析命令种类
#define NORMAL 0        // 一般的命令
#define OUT_REDIRECT 1  // 带输出重定向命令
#define IN_REDIRECT 2   // 带输入重定向命令
#define PIPE 3     // 带管道命令

// 保存当前的命令行输入
#define BUFFSIZE 256
char line[BUFFSIZE];
//存储当前路劲
char buffer[BUFFSIZE];
//存储用户名,主机名
char hostname[BUFFSIZE] = { '\0' };
// 根据命令行输入得到的命令参数
char args[10][80];
// 计数,命令中词汇数量
int argcount = 0;
// 打印提示符
void Print_prompt(); 
// 读取用户输入的命令
void Get_input(char *); 
// 解析输入的命令
void Explain_command(char *, int *, char (*a)[80]); 
//执行命令
void Do_cmd(int, char a[][80]);  
// 查找命令中的可执行程序
int Find_command(char *);   
// 执行 cd 命令
void exec_cd();
//执行help
void exec_help();
int main(int argc, char *argv[])
{
 while(1)
    {
        memset(line, 0, BUFFSIZE); //清空数组
        Print_prompt(); // 打印提示符
        Get_input(line); // 读取用户输入
        if(strcmp(line, "exit")==0 || strcmp(line, "quit")==0)
             break;
		if(strcmp(line, "help")==0)
		{
			exec_help();
			continue;
		}
        int i = 0;
        args[i][0] = '\0';
        argcount = 0;// 计数,命令中词汇数量
        Explain_command(line, &argcount, args); // 解析命令
		if(strncmp(line, "cd",2)==0)
		{
		exec_cd();
		continue;
		}
        Do_cmd(argcount, args);// 执行命令
    }
	return 0;
} 
void Print_prompt()
{
	int uid = getuid(); //获取用户识别码
	struct passwd *user;
	user= getpwuid(uid);//
	memset(buffer, 0, BUFFSIZE);
	memset(hostname, 0, BUFFSIZE);
    getwd(buffer);//获取当前路径
	gethostname(hostname, sizeof(hostname));//获取主机名
	printf(L_GREEN "%s@%s:"L_PURPLE"%s"WHITE"$" NONE,
		   user->pw_name,hostname,buffer);
}	

void Get_input(char *line)
{
     fgets(line, BUFFSIZE, stdin);        
	 int len = strlen(line); // 计入换行符\n,不计入结束符\0
     if(len >= BUFFSIZE)
     {
        printf(L_RED"ERROR: command is too long !\n" NONE);
        exit(-1);
     }
     line[len-1] = '\0'; // 去除读入的换行符
}
// 解析line中的命令,结果存入args中,命令及其参数个数为argcount
// 如,"ls -l"命令,则args[0]、args[1]分别为ls、-l
void Explain_command(char *line, int *argcount, char args[][80])
 {
	 // 将用户输入的整串字符串拆分为一个个单词
     // 存入二维数组的每一行中
     char *p = line;
     while(*p != '\0')
         {
             if(*p == ' ')
			 {
                 p++;
			 }
             else
			 {
				char *q = p;
				int len = 0; // 单词长度
				while((*q!=' ') && (*q!='\0'))
				{
					q++; len++;
				}
				// 将当前拆解的单词存入二维数组中的一行
				strncpy(args[*argcount], p, len+1);
				args[*argcount][len] = '\0';
				(*argcount)++;
				p = q;
             }
        }
 }
 
 void Do_cmd(int argcount, char args[10][80])
 {
     // 指针数组,每个元素指向二维数组中的一行
     // arg存放所有命令及其参数,argnext存放管道符后的命令
     char *arg[argcount+1], *argnext[argcount+1];
     int i;
	 int flag = 0;//输入命令合法性检测标志
	 int status = 0; //命令种类标志
	 int background = 0; //后台运行符标帧
     char *file;//重定向文件名
     pid_t pid;
 
     // 提取命令
     for(i=0; i<argcount; i++)
        arg[i] = args[i];
     arg[argcount] = NULL;
 
     // 查看命令行是否有后台运行符
	 // 后台运行符必须在命令的末尾,否则命令格式错误
     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(L_RED"ERROR: wrong command about backgrount\n" NONE);
                         return;
                     }
            }
         }
 
     for(i=0; arg[i]!=NULL; i++)
     {
         if(strcmp(arg[i], ">") == 0)
        {
             flag++;
             status = OUT_REDIRECT;
             if(arg[i+1] == NULL)    // 输出重定向符在最后面
                flag++; // 使flag大于1,告知命令格式错误
        }
         if(strcmp(arg[i], "<") == 0)
         {
             flag++;
             status = IN_REDIRECT;
             if(i == 0)      // 输入重定向符在最前面
                flag++;
         }
		if(strcmp(arg[i], "|") == 0)
         {
             flag++;
             status = PIPE;
             if(arg[i+1] == NULL)    // 管道符在最后面
                 flag++;
             if(i == 0)      // 管道符在最前面
                 flag++;
         }
     }
     // flag大于1,说明同时含有>,<,|中的两个或以上,本程序不支持
     // 或者命令格式错误
    if(flag > 1)
     {
         printf(L_RED"ERROR: wrong command about >,<,|\n"NONE);
         return;
     }

     if(status == OUT_REDIRECT) // 命令中只含有一个输出重定向符
     {
         for(i=0; arg[i]!=NULL; i++)
         if(strcmp(arg[i], ">") == 0)
         {
             file = arg[i+1]; // 获取输出重定向的文件名
             arg[i] = NULL;
         }
     }
 
     if(status == IN_REDIRECT)  // 命令中只含有一个输入重定向符
     {
        for(i=0; arg[i] != NULL; i++)
         if(strcmp(arg[i], "<") == 0)
         {
             file = arg[i+1];
             arg[i] = NULL;
         }
     }
 
    if(status == PIPE)    // 命令中只含有一个管道符号
     {
         for(i=0; arg[i]!=NULL; i++)
         if(strcmp(arg[i], "|") == 0)
         {
             arg[i] = NULL;
             i++;
             int j = 0;
              // 将管道符后面的命令存入argnext中
             while(arg[i] != NULL)
                 {
                     argnext[j++] = arg[i++];
                 }
				 argnext[j] = NULL;
                 break;
         }
     }

     pid = fork();   // 创建子进程
     if(pid < 0)
     {
             perror(L_RED"fork failure" NONE);
             return;
     }
 
     switch(status)
     {
         case NORMAL: // 一般命令
                 if(pid==0)      // 子进程执行用户输入的命令
                 {
                     if(!Find_command(arg[0]))       // 判断命令是否可执行
                     {
                        printf(L_RED"%s: command not found\n" NONE, arg[0]);
                        exit(0);
                     }
                     execvp(arg[0], arg);    // execvp 开始执行命令 
                     exit(0);
                 }
                 break;
        case OUT_REDIRECT: // 命令中含有输出重定向符
                 if(pid == 0)
                  {
                    if(!Find_command(arg[0]))
                     {
                         printf(L_RED"%s: command not found\n" NONE, arg[0]);
                         exit(0);
                     }
                     // 打开或新建输出重定向的文件
                 int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0644);
                     // 将标准输出复制到打开的文件描述符,即用文件描述符替换标准输出
                     dup2(fd, 1);
                     execvp(arg[0], arg); 
                     exit(0);
                  }
                 break;
        case IN_REDIRECT: // 命令中含有输入重定向符
                 if(pid == 0)
                 {
                     if(!Find_command(arg[0]))
                     {
                         printf(L_RED"%s: command not found\n" NONE, arg[0]);
                         exit(0);
                     }
                 int fd = open(file, O_RDONLY);
				 // 将标准输入复制到打开的文件描述符,即用文件描述符替换标准输入
                 dup2(fd, 0);
                 execvp(arg[0], arg);
                 exit(0);
                 }
                 break;
         case PIPE: // 命令中含有管道符
             if(pid == 0)    // 子进程
             {
                 pid_t pid2;
                 int fd2;
                 if((pid2=fork()) < 0)   // 当前子进程中在新建一个子进程
                 {
                    perror("fork2 failure");
                     return;
				 }
                 if(pid2 == 0)   // 新建的子进程执行管道符前面的命令
                 {
                     if(!Find_command(arg[0]))
                     {
                         printf(L_RED"%s: command not found\n" NONE, arg[0]);
                         exit(0);
                     }
                      // 将管道符前的命令执行结果存入fd2中
                     fd2 = open("/tmp/youdontknowfile",
                         O_WRONLY | O_CREAT | O_TRUNC, 0644);
                     dup2(fd2, 1);   // 重定向标准输出
                     execvp(arg[0], arg);
                     exit(0);
                 }
                 waitpid(pid2, NULL, 0); // 等待管道符前的命令执行返回
                 if(!Find_command(argnext[0]))
                  {
                     printf(L_RED"%s: command not found\n" NONE, argnext[0]);
                     exit(0);
                  }
                 fd2 = open("/tmp/youdontknowfile", O_RDONLY);
                 dup2(fd2, 0);   // 将fd2定义为标准输入
                 execvp(argnext[0], argnext);    // 执行管道符后面的命令
                 exit(0);
             }
                break;
             default:
                break;
     }
     // 命令中有后台运行符,则父进程直接返回,不等待子进程返回
     if(background == 1)
     {
             printf(L_RED"[process id %d]\n" NONE, pid);
             return;
     }
     waitpid(pid, NULL, 0);  // waitpid 父进程等待子进程返回
 }
 
 // 判断命令是否可执行,是否有对应的可执行文件
int Find_command(char *command)
{
     DIR *dir;
     struct dirent *ptr;
     char *path[] = {"./", "/bin", "/usr/bin", NULL};
     // 当输入命令"./build"时,将build命令与目录中的build文件进行匹配
     if(strncmp(command, "./", 2) == 0)
         command = command + 2;
     int i = 0;
     while(path[i] != NULL)
     {
         if((dir=opendir(path[i])) == NULL)      // 打开目录
             printf("cannot open /bin\n");
         while((ptr=readdir(dir)) != NULL)       // 读取目录中的文件列表
             if(strcmp(ptr->d_name, command) == 0)
                 {
                     closedir(dir);
                     return 1;
                 }
             closedir(dir);
             i++;
     }
        return 0;
}
//执行cd命令
void exec_cd() {
    if (argcount != 2) 
	{
		printf(L_RED"ERROR: wrong command about cd\n"NONE);
		return;
	}
    chdir(args[1]);
}
//执行help
void exec_help(){
printf(L_RED"Shell支持/bin 和 /usr/bin 目录下的所有命令!\n具体命令详细介绍按如下输入:\n" L_PURPLE"commond --help\n"NONE);
}
  • 4
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

威威攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值