您的位置:首页 > 运维架构 > Shell

虚拟shell终端程序

2008-11-04 02:41 267 查看
一,实验内容
实现一个简单的shell终端程序
功能:能在虚拟shell界面下响应一些简单的shell命令
二.对linux环境shell工作流程的分析

提示输入
获取用户指令
解析指令
寻找命令文件
执行命令

(1)提示输入
(2)获取用户指令
(3)解析指令
(4)寻找命令文件
(5)执行命令
三.对上述五个过程的详细说明
(1)在虚拟的shell界面中出现命令提示符($或#)
path = get_current_dir_name();
printf("%s>$",path);
说明:函数get_current_dir_name:获取当前目录名,返回值是一个指向当前目录绝对路径的字符串指针
(2)获取用户指令:获取用户在命令提示符后面输入的命令和参数的最大长度
/*开始获取输入*/
li_inputlen = 0;
lc_char = getchar();
while (lc_char !='/n'){
if(li_inputlen < BUFFERSIZE)
buffer[li_inputlen++] = lc_char;
lc_char = getchar();
}

/*命令超长处理*/
if (li_inputlen >= BUFFERSIZE){
printf("Your command is too long! Please re-enter your command!/n");
li_inputlen = 0; /*reset */
continue;
}
else
buffer[li_inputlen] = '/0';/*加上串结束符号,形成字串*/

/*将命令从缓存拷贝到input中*/
input = (char *) malloc(sizeof(char) * (li_inputlen+1));
strcpy(input,buffer);
说明:在这个过程中,我们主要是接收用户键入的命令,但是在这里我们必须考虑用户键入命令过长超出了BUFFERSIZE的范围。此段程序没有太多需要解释,主要就是对命令的接收,已经对异常输入的处理,引入input是为了下一步的处理需要。
(3)解析指令:对用户输入的命令进行解析,解析出命令名
/* 获取命令和参数并保存在arg中*/
for (i = 0,j = 0,k = 0;i <= li_inputlen;i++){
/*管道和重定向单独处理*/
if (input[i] == '<' || input[i] == '>' || input[i] =='|'){
if (input[i] == '|')
pipel(input,li_inputlen);
else
redirect(input,li_inputlen);
is_bj = 1;
break;
}
/*处理空格、TAB和结束符。不用处理‘/n',因为回车符并没有写入buffer*/
if (input[i] == ' ' || input[i] =='/t' || input[i] == '/0'){
if (j == 0) /*这个条件可以略去连在一起的多个空格或者tab*/
continue;
else{
buffer[j++] = '/0';
arg[k] = (char *) malloc(sizeof(char)*j);
/*将指令或参数从缓存拷贝到arg中*/
strcpy(arg[k],buffer);
j = 0; /*准备取下一个参数*/
k++;
}
}
else{
/*如果字串最后是‘&',则置后台运行标记为1*/
if (input[i] == '&' && input[i+1] == '/0'){
is_back = 1;
continue;
}
buffer[j++] = input[i];
}
}

free(input);/*释放空间*/
说明:上述程序所实现的功能是将存在同一个字符串里的命令和参数分离,在做上述操作之前对输入输出重定向以及管道进行了单独处理
(4)寻找命令文件:每个命令的执行都必须依靠对应的可执行文件,路径存放在用户的path环境变量中;
/*如果输入的指令是leave则退出while,即退出程序*/
if (strcmp(arg[0],"leave") == 0 ){
printf("bye-bye/n");
break;
}
/*如果输入的指令是about则显示作者信息,同时结束本条命令的解析过程*/
if (strcmp(arg[0]," about") == 0 ){
printf("copyright by shike,shike13@163.com/n");
continue;
}

if (is_bj == 0){ /*非管道、重定向指令*/
/*在使用xxec执行命令的时候,最后的参数必须是NULL指针,
*所以将最后一个参数置成空值*/
arg[k] = (char *) 0;
/*判断指令arg[0]是否存在*/
if (is_fileexist(arg[0]) == -1 ){
printf("This command is not found?!/n");
for(i=0;i<k;i++)
free(arg[i]);
continue;
}
说明:首先对特殊指令leave和about的处理,然后寻找可执行文件,如果不存在,释放空间,进入下一条指令的处理流程。
(5)执行命令:可通过fork()系统调用创建一个进程来完成执行命令的任务,具体的命令执行用exev()函数。
/* fork a sub-process to run the execution file */
if ((pid = fork()) ==0) /*子进程*/
execv(buffer,arg);
else /*父进程*/
if (is_back == 0) /*并非后台执行指令*/
waitpid(pid,&status,0);

/*释放申请的空间*/
for (i=0;i<k;i++)
free(arg[i]);
}
说明:执行程序的过程相对比较简单,调用fork()创建进程,exev()执行,处理完成之后千万不要忘了释放申请的内存空间。因为本程序为无限循环,如果不释放空间,多次执行后可能会产生大量垃圾,影响系统执行速度。
四.程序中出现的部分函数
(1)根据环境变量,查找可执行文件
int is_fileexist(char *comm)
{
char *path,*p;
int i;

i = 0;
/*使用getenv函数来获取系统环境变量,用参数PATH表示获取路径*/
path = getenv("PATH");
p = path;
while (*p != '/0'){
/*路径列表使用‘:’来分隔路径*/
if (*p != ':')
buffer[i++] = *p;
else{
buffer[i++] = '/';
buffer[i] = '/0';
/*将指令和路径合成,形成pathname,并使用access函数来判断该文件是否存在*/
strcat(buffer,comm);

if (access(buffer,F_OK) == 0) /*文件被找到*/
return 0;
else
/*继续寻找其它路径*/
i = 0;
}
p++;
}
/*搜索完所有路径,依然没有找到则返回-1*/
return -1;
}
(2)输入输出重定向的处理
/*感谢Liecs以及wKernel在这个函数上给我的提示*/
int redirect(char *in,int len)
{
char *argv[30],*filename[2];
pid_t pid;
int i,j,k,fd_in,fd_out,is_in = -1,is_out = -1,num = 0;
int is_back = 0,status=0;

/*这里是重定向的命令解析过程,其中filename用于存放重定向文件,
*is_in, is_out分别是输入重定向标记和输出重定向标记*/
for (i = 0,j = 0,k = 0;i <= len;i++){
if (in[i]==' '||in[i]=='/t'||in[i]=='/0'||in[i] =='<'||in[i]=='>'){
if (in[i] == '>' || in[i] == '<'){
/*重定向指令最多'<','>'各出现一次,因此num最大为2,
*否则认为命令输入错误*/
if (num < 3){
num ++;
if (in[i] == '<')
is_in = num - 1;
else
is_out = num - 1;

/*处理命令和重定向符号相连的情况,比如ls>a*/
if (j > 0 && num == 1) {
buffer[j++] = '/0';
argv[k] = (char *) malloc(sizeof(char)*j);
strcpy(argv[k],buffer);
k++;
j = 0;
}
}
else{
printf("The format is error!/n");
return -1;
}
}
if (j == 0)
continue;
else{
buffer[j++] = '/0';
/*尚未遇到重定向符号,字符串是命令或参数*/
if (num == 0){
argv[k] = (char *) malloc(sizeof(char)*j);
strcpy(argv[k],buffer);
k++;
}
/*是重定向后符号的字符串,是文件名*/
else{
filename[status] = (char *) malloc(sizeof(char)*j);
strcpy(filename[status++],buffer);
}
j = 0; /*initate*/
}
}
else{
if (in[i] == '&' && in[i+1] == '/0'){
is_back = 1;
continue;
}
buffer[j++] = in[i];
}
}

argv[k] = (char *) 0;

if (is_fileexist(argv[0]) == -1 ){
printf("This command is not founded!/n");
for(i=0;i<k;i++)
free(argv[i]);
return 0;
}

if ((pid = fork()) ==0){
/*存在输出重定向*/
if (is_out != -1)
if((fd_out=open(filename[is_out],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1){
printf("Open out %s Error/n",filename[is_out]);
return -1;
}

/*存在输入重定向*/
if (is_in != -1)
if((fd_in=open(filename[is_in],O_RDONLY,S_IRUSR|S_IWUSR))==-1){
printf("Open in %s Error/n",filename[is_out]);
return -1;
}

if (is_out != -1)
/*使用dup2函数将标准输出重定向到fd_out上,dup2(int oldfd,int newfd)实现的
*是把oldfd所指的文件描述符复制到newfd。若newfd为一已打开的文件描述词,
*则newfd所指的文件会先被关闭,dup2复制的文件描述词与原来的文件描述词
*共享各种文件状态*/
if(dup2(fd_out,STDOUT_FILENO)==-1){
printf("Redirect Standard Out Error/n");
exit(1);
}

if (is_in != -1)
if(dup2(fd_in,STDIN_FILENO)==-1){
printf("Redirect Standard Out Error/n");
exit(1);
}
execv(buffer,argv);
}
else
if (is_back == 0) /*run on the TOP*/
waitpid(pid,&status,0);

for (i=0;i<k;i++)
free(argv[i]);

if (is_in != -1){
free(filename[is_in]);
close(fd_in);
}
if (is_out != -1){
free(filename[is_out]);
close(fd_out);
}
return 0;
}
(3) 管道的命令解析过程
int pipel(char *input,int len)
{
char *argv[2][30];
int i,j,k,count,is_back = 0;
int li_comm = 0,fd[2],fpip[2];
char lc_char,lc_end[1];
pid_t child1,child2;

/*管道的命令解析过程*/
for (i = 0,j = 0,k = 0;i <= len;i++){
if (input[i]== ' ' || input[i] == '/t' || input[i] == '/0' || input[i] == '|'){
if (input[i] == '|' ) /*管道符号*/
{
if (j > 0)
{
buffer[j++] = '/0';
/*因为管道连接的是两个指令,所以用二维数组指针来存放命令和参数,
*li_comm是表示第几个指令*/
argv[li_comm][k] = (char *) malloc(sizeof(char)*j);
strcpy(argv[li_comm][k++],buffer);
}
argv[li_comm][k++] = (char *) 0;
/*遇到管道符,第一个指令完毕,开始准备接受第二个指令*/
li_comm++;
count = k;
k=0;j=0;
}
if (j == 0)
continue;
else
{
buffer[j++] = '/0';
argv[li_comm][k] = (char *) malloc(sizeof(char)*j);
strcpy(argv[li_comm][k],buffer);
k++;
}
j = 0; /*initate*/
}
else{
if (input[i] == '&' && input[i+1] == '/0'){
is_back = 1;
continue;
}
buffer[j++] = input[i];
}
}
argv[li_comm][k++] = (char *) 0;

if (is_fileexist(argv[0][0]) == -1 ){
printf("This first command is not found!/n");
for(i=0;i<count;i++)
free(argv[0][i]);
return 0;
}
/*指令解析结束*/

/*建立管道*/
if (pipe(fd) == -1 ){
printf("open pipe error!/n");
return -1;
}

/*创建第一个子进程执行管道符前的指令,并将输出写到管道*/
if ((child1 = fork()) ==0){
/*关闭读端*/
close(fd[0]);
if (fd[1] != STDOUT_FILENO){
/*将标准输出重定向到管道的写入端,这样该子进程的输出就写入了管道*/
if (dup2(fd[1],STDOUT_FILENO) == -1){
printf("Redirect Standard Out Error/n");
return -1;
}
/*关闭写入端*/
close(fd[1]);
}
execv(buffer,argv[0]);
}
else{ /*父进程*/
/*先要等待写入管道的进程结束*/
waitpid(child1,&li_comm,0);
/*然后我们必须写入一个结束标记,告诉读管道进程数据到这里就完了*/
lc_end[0] = 0x1a;
write(fd[1],lc_end,1);
close(fd[1]);

if (is_fileexist(argv[1][0]) == -1 ){
printf("This command is not founded!/n");
for(i=0;i<k;i++)
free(argv[1][i]);
return 0;
}

/*创建第二个进程执行管道符后的指令,并从管道读输入流 */
if ((child2 = fork()) == 0){
if (fd[0] != STDIN_FILENO){
/*将标准输入重定向到管道读入端*/
if(dup2(fd[0],STDIN_FILENO) == -1){
printf("Redirect Standard In Error!/n");
return -1;
}
close(fd[0]);
}
execv(buffer,argv[1]);
}
else /*父进程*/
if (is_back == 0)
waitpid(child2,NULL,0);
}
for (i=0;i<count;i++)
free(argv[0][i]);
for (i=0;i<k;i++)
free(argv[1][i]);
return 0;
}

六.运行界面以及结果分析
ls,mkdir,vi命令的使用

用虚拟shell终端打开vi编辑器

mkdir,rmdir,rm,find,leave命令的使用截图
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: