在linux环境下,编写自己的shell
1.要求:
- 实现单个命令,如: ls.
- 命令可以带参数,如:ls -l /tmp
- 可以带有一个输入或者输出的重定向。
- 可以带一个管道符命令
- 可以后台运行
- 输入exit或logout退出。
2.错误处理:
- 输入错误的命令格式报错
- 输入不存在的命令报错
3.关键函数功能:
1. 打印myshell前标(print_prompt):
模仿shell中打印当前目录的前标。
2. 获取用户的输入(get_input):
获得一条用户输入的命令,将参数包括中间的空格整体存入一个一位数组buf当中,等待下一个函数进行参数解析,并判断如果参数长度若大于256则终止程序,输入的命令以‘\n’为结束标志。
3. 参数解析函数(explain_input):
将get_input函数中得到的命令整体传入参数解析函数,并向该函数中传入一个二位字符数组的地址用来保存解析出来的命令,需要注意的是该二位数组的内存空间应当是在主函数中已经开辟好的。
注意:由于参数解析是通过空格将命令和参数以及输入输出重定向等命令、参数分开的,所以当遇上带有空格的目录时,参数解析的时候空格会将一个目录名分开,将其解析为两个参数,所以我们可以模仿shell的办法如下图:
shell的办法是将空格进行转义,所以我们也可以去模仿它,在输入目录的空格之前先输入一个 ‘\’ ,然后输入空格,于是在参数解析时当遇到 ‘\’的时候我们将其之后的空格跳过之后就可以了,但是在测试chdir函数时又发现了问题,当把带有 \ 的目录名当做chdir函数的参数改变当前目录的时候会发现报错:该目录不存在,原来‘\ ‘只是shell终端的转义,在目录名中并不需要进行转义,只要按原有的格式直接输入就好,所以我们要做的不只是将 \ 后的空格保留,还要将空格之后的所有字符前移并将 \ 覆盖掉。具体代码实现如下:
if(q[0] == 92) //当遇上 \
{
q[0] = ' '; //用空格将之前的 \ 覆盖
q[1] = q[2]; //与下面的循环一起,将之后的所有字符前移一位
for(i = 2; ; i++)
{
q[i] = q[i+1];
if((q[i] == ' ') || (q[i] == '\n')) //如果当前要前移的参数正好是空格或者是'\n'则结束
break;
}
}
4. 执行命令函数(do_cmd):
前面参数解析函数已将解析好的参数存入二维数组当中,这个函数将执行命令,参数argcount记录了命令所带的参数的个数。
5.命令查找函数(find_command):
在当前目录,/bin,/usr/bin 目录下查找命令的可执行程序
下面附上代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<dirent.h>
#define normal 0 //一般的命令
#define out_readirect 1 //输出重定向
#define in_readdirect 2 //输入重定向
#define have_pipe 3 //命令中有管道
void print_prompt(); //打印提示符
void get_input(char *); //得到输入的命令
void explain_input(char *, int *, char (*)[256]); //对输入的命令进行解析
void do_cmd(int, char (*)[256]); //执行命令
int find_command(char *); //查找命令中的可执行程序
char *msg; //用于myshell提示信息的输出
int main(int argc, char **argv)
{
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");
exit(-1);
}
while(1)
{
memset(buf, 0, 256); //将buf所指的空间清零
print_prompt();
get_input(buf);
if( strcmp(buf, "exit\n") == 0 || strcmp(buf, "logout\n") == 0)
{
break;
}
for(i = 0; i < 100; i++)
{
arglist[i][0] = '\0';
}
argcount = 0;
explain_input(buf, &argcount, arglist);
do_cmd(argcount, arglist);
}
if(buf != NULL)
{
free(buf);
buf = NULL;
}
exit(0);
}
void print_prompt()
{
msg = (char *)malloc(100);
getcwd(msg, 100);
printf("myshell@zxq:%s$ ", msg);
free(msg);
}
//获取用户输入
void get_input(char *buf)
{
int len = 0;
int ch;
ch = getchar();
while(len < 256 && ch != '\n')
{
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结束
void explain_input(char *buf, int *argcount, char (*arglist)[256])
{
char *p = buf;
char *q = buf;
int number = 0;
int i;
while(1)
{
if(p[0] == '\n')
{
break;
}
if(p[0] == ' ')
{
p++;
}
else
{
q = p;
number = 0;
while((q[0] != ' ') && (q[0] != '\n'))
{
if(q[0] == 92)
{
q[0] = ' ';
q[1] = q[2];
for(i = 2; ; i++)
{
q[i] = q[i+1];
if((q[i] == ' ') || (q[i] == '\n'))
break;
}
}
number++;
q++;
}
strncpy(arglist[*argcount], p, number + 1);
arglist[*argcount][number] = '\0';
*argcount = *argcount + 1;
p = q;
}
}
}
void do_cmd(int argcount, char (*arglist)[256])
{
int flag = 0;
int how = 0; //用于只是命令中是否含有> 、 < 、 |
int background = 0; //标识命令中是否有后台运行的标示符
int status;
int i;
int fd;
char *arg[argcount + 1];
char *argnext[argcount + 1];
pid_t pid;
char *file;
//将命令取出
for(i = 0; i < argcount; i++)
{
arg[i] = (char *)arglist[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("wrong command\n");
return ;
}
}
}
for(i = 0; arg[i] != NULL; i++)
{
if(strcmp(arg[i], ">") == 0)
{
flag++;
how = out_readirect;
if(arg[i + 1] == NULL)
{
flag++;
}
}
if(strcmp(arg[i], "<") == 0)
{
flag++;
how = in_readdirect;
if(i == 0)
{
flag++;
}
}
if(strcmp(arg[i], "|") == 0)
{
flag++;
how = have_pipe;
if(arg[i+1] == NULL)
{
flag++;
}
if(i == 0)
{
flag++;
}
}
}
//flag大于1,说明命令中含有多个> < |符号,本程序是不支持这样的命令的,或命令格式不对
if(flag > 1)
{
printf("wrong command\n");
return ;
}
if(how == out_readirect)
{
//命令只含有一个输出重定向符号
for(i = 0; arg[i] != NULL; i++)
{
if(strcmp(arg[i], ">") == 0)
{
file = arg[i+1];
arg[i] = NULL;
}
}
}
if(how == in_readdirect)
{
//命令只含有一个输入重定向
for(i = 0; arg[i] != NULL; i++)
{
if(strcmp(arg[i], "<") == 0)
{
file = arg[i + 1];
arg[i] == NULL;
}
}
}
if(how == have_pipe)
{
//命令只有一个管道符号,把管道符后面的部分存入argnext中,管道后面的部分是一个可执行的shell命令
for(i = 0; arg[i] != NULL; i++)
{
if(strcmp(arg[i], "|") == 0)
{
arg[i] = NULL;
int j;
for(j = i + 1; arg[j] != NULL; j++)
{
argnext[j-i-1] = arg[j];
}
argnext[j-i-1] = arg[j];
break;
}
}
}
if((arg[0] != NULL) && (strcmp(arg[0], "cd") == 0))
{
if(arg[1] == NULL)
{
return ;
}
if(strcmp(arg[1], "~") == 0)
{
strcpy(arg[1], "/home/zhuxinquan/");
}
if(chdir(arg[1]) == -1)
{
perror("cd");
}
return ;
}
if((pid = fork()) < 0)
{
printf("fork error\n");
return ;
}
switch(how)
{
case 0:
//pid为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);
dup2(fd, 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 process 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;
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;
}