使用方法
- 在Ubuntu下创建一个myshell.c文件 touch myshell.c
- 复制代码到myshell.c文件中
- 编译c文件 gcc -o myshell myshell.c
- 执行文件 ./myshell
- 输入shell支持的有效命令
- 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);
}