【Unix/Linux编程实践】shell如何运行程序—编写命令解析器sh
来源:互联网 发布:淘宝pc端 编辑:程序博客网 时间:2024/06/04 18:59
1.shell是如何运行程序的?
shell由下面的循环组成:
while (!end_of_input) getcommand execute command wait for command to finish
我们可以实际用一下shell:
jiange@jiange-Inspiron:~/cpp$ lsoverride override.cpp test test1.cpp test.cpp test.o test.shjiange@jiange-Inspiron:~/cpp$ ps PID TTY TIME CMD 2457 pts/12 00:00:00 bash 3780 pts/12 00:00:00 ps
上述过程为:
sh读入ls,新建一个进程ls并执行,此时sh则等待ls的退出,ls退出后回到sh,sh继续读入ps……
因此,要编写sh,我们需要知道如何运行一个程序,创建,终止一个进程,以及一个进程如何等待另一个进程的结束。
2.如何执行一个程序
答案:
execvp(progname, arglist)
它的运作流程:
- 程序调用execvp;
- 内核从磁盘将程序载入;
- 内核将arglist复制到进程;
- 内核调用main(argc, argv)。
运行ls的例子:
/* exec1.c - shows how easy it is for a program to run a program */main(){ char *arglist[3]; arglist[0] = "ls"; arglist[1] = "-l"; arglist[2] = 0 ; printf("* * * About to exec ls -l\n"); execvp( "ls" , arglist ); printf("* * * ls is done. bye\n");}
执行结果:
$ ./exec1 * * * About to exec ls -l总用量 40-rwxrwxr-x 1 jiange jiange 7370 12月 7 22:59 exec1-rw-rw-r-- 1 jiange jiange 262 10月 14 2007 exec1.c-rw-rw-r-- 1 jiange jiange 406 10月 14 2007 forkdemo1.c-rw-rw-r-- 1 jiange jiange 315 10月 14 2007 forkdemo2.c-rw-rw-r-- 1 jiange jiange 503 10月 14 2007 forkdemo3.c-rw-rw-r-- 1 jiange jiange 1395 10月 14 2007 psh1.c-rw-rw-r-- 1 jiange jiange 1766 10月 14 2007 psh2.c-rw-rw-r-- 1 jiange jiange 784 10月 14 2007 waitdemo1.c-rw-rw-r-- 1 jiange jiange 1077 10月 14 2007 waitdemo2.c
一切好像正常运行,不过,为什么printf(“* * * ls is done. bye\n”);这一句没有执行呢?!
这就要理解下execvp的作用了:execvp将程序从磁盘载入当前进程中,替代当前进程的代码和数据,所以执行完execvp之后,进程exec1完全被替换掉了,自然不会执行后续的打印工作。
2.带提示符的shell
这个版本提示用户输入程序名和参数,然后运行。
/* prompting shell version 1 * Prompts for the command and its arguments. * Builds the argument vector for the call to execvp. * Uses execvp(), and never returns. */#include <stdio.h>#include <signal.h>#include <string.h>#define MAXARGS 20 /* cmdline args */#define ARGLEN 100 /* token length */int main(){ char *arglist[MAXARGS+1]; /* an array of ptrs */ int numargs; /* index into array */ char argbuf[ARGLEN]; /* read stuff here */ char *makestring(); /* malloc etc */ numargs = 0; while ( numargs < MAXARGS ) { printf("Arg[%d]? ", numargs); if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' ) arglist[numargs++] = makestring(argbuf); else { if ( numargs > 0 ){ /* any args? */ arglist[numargs]=NULL; /* close list */ execute( arglist ); /* do it */ numargs = 0; /* and reset */ } } } return 0;}int execute( char *arglist[] )/* * use execvp to do it */{ execvp(arglist[0], arglist); /* do it */ perror("execvp failed"); exit(1);}char * makestring( char *buf )/* * trim off newline and create storage for the string */{ char *cp, *malloc(); buf[strlen(buf)-1] = '\0'; /* trim newline */ cp = malloc( strlen(buf)+1 ); /* get memory */ if ( cp == NULL ){ /* or die */ fprintf(stderr,"no memory\n"); exit(1); } strcpy(cp, buf); /* copy chars */ return cp; /* return ptr */}
上面使用makestring函数来动态分配空间给字符串参数。
运行上面程序,我们就可以传递参数进去了。
不过一个比较严重的缺陷:运行完ls之后,整个程序就退出了,而我们期待的是,运行完ls后返回到我们的shell中,shell可以继续接收命令输入并执行。
在开头我们讲到,sh读入ls,新建一个进程ls并执行,此时sh则等待ls的退出,ls退出后回到sh。
也就是说,sh并不直接执行ls命令,而是新建了一个进程来执行它,那么问题来了:如何建立新的进程?
答案:使用fork();
我们可以使用fork来创建子进程,然后新的进程调用execvp来执行用户命令。
那么,父进程sh如何等待子进程ls的结束呢?
答案:使用 pid=wait(&status);
wait阻塞调用它的进程直到子进程结束,之后wait取得子进程结束时传给exit的值(通过status),并返回子进程的PID。
3.可以持续接收用户命令的shell
经过以上分析,我们可以设计新的shell:
获取命令-> 用fork创建新进程 ->父进程wait ->子进程exec运行新程序->在新程序的main中运行->新程序exit -> 父进程得到子进程状态->获取命令
/** prompting shell version 2 ** ** Solves the `one-shot' problem of version 1 ** Uses execvp(), but fork()s first so that the ** shell waits around to perform another command ** New problem: shell catches signals. Run vi, press ^c. **/#include <stdio.h>#include <signal.h>#define MAXARGS 20 /* cmdline args */#define ARGLEN 100 /* token length */main(){ char *arglist[MAXARGS+1]; /* an array of ptrs */ int numargs; /* index into array */ char argbuf[ARGLEN]; /* read stuff here */ char *makestring(); /* malloc etc */ numargs = 0; while ( numargs < MAXARGS ) { printf("Arg[%d]? ", numargs); if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' ) arglist[numargs++] = makestring(argbuf); else { if ( numargs > 0 ){ /* any args? */ arglist[numargs]=NULL; /* close list */ execute( arglist ); /* do it */ numargs = 0; /* and reset */ } } } return 0;}execute( char *arglist[] )/* * use fork and execvp and wait to do it */{ int pid,exitstatus; /* of child */ pid = fork(); /* make new process */ switch( pid ){ case -1: perror("fork failed"); exit(1); case 0: execvp(arglist[0], arglist); /* do it */ perror("execvp failed"); exit(1); default: while( wait(&exitstatus) != pid ) ; printf("child exited with status %d,%d\n", exitstatus>>8, exitstatus&0377); }}char *makestring( char *buf )/* * trim off newline and create storage for the string */{ char *cp, *malloc(); buf[strlen(buf)-1] = '\0'; /* trim newline */ cp = malloc( strlen(buf)+1 ); /* get memory */ if ( cp == NULL ){ /* or die */ fprintf(stderr,"no memory\n"); exit(1); } strcpy(cp, buf); /* copy chars */ return cp; /* return ptr */}
关于格式控制方面,比如如何在一行中输入参数,如何用exit退出shell等等,在这里我们不做讨论。
在运行的时候,我们发现一个严重的错误:
假如我们正在运行一个子进程,这时我们键入Ctrl-C键,我们预期的应该是子进程终止,而shell仍继续运行,但事实是shell也终止了,因为键盘信号SIGINT发给了所有连接的进程!
为使得有子进程在运行时,shell不会因为中断信号而跟着终止,我觉得可以这样:只要有子进程运行,shell就将对SIGINT信号的响应设为ignore,子进程结束之后,则设置为默认方式。这样子shell就能在子进程运行的时候屏蔽中断信号。
- 【Unix/Linux编程实践】shell如何运行程序—编写命令解析器sh
- 《unix/linux编程实践教程》学习笔记:第八章 编写命令解释器sh
- Understanding Unix/Linux Programming 笔记:chapter 8:进程和程序:编写命令解释器sh
- 【Unix/Linux编程实践】从零做起:编写who命令
- 【Unix/Linux编程实践】文件系统:编写pwd
- linux入门-shell(.sh)脚本编写和运行
- 《unix/linux编程实践教程》学习笔记:第二章 编写who,cp命令
- unix/linux编程实践教程--more命令
- unix/linux编程实践教程:who命令
- unix/linux编程实践教程:ls命令
- unix/linux编程实践教程:pwd命令
- 如何运行linux shell程序
- 深入浅出Shell 编程:Unix/Linux 命令
- 深入浅出Shell 编程:Unix/Linux 命令
- 深入浅出Shell 编程:Unix/Linux 命令
- 《unix/linux编程实践教程》之Shell编程一
- 《unix/linux编程实践教程》之Shell编程二
- linux部署sh命令编写
- 走迷宫问题
- 跳转appStore
- 理解响应式布局
- IDE开发环境下成功,而linux下,g++失败
- dfsf
- 【Unix/Linux编程实践】shell如何运行程序—编写命令解析器sh
- 添加远程库与Maven的依赖关系
- 清华EMBA课程系列思考之三 -- 中国经济与金融
- C#反射详解
- sublime 一些资源及快捷键
- while循环和for循环的区别
- C#与Java的时间转换
- ubuntu 开机出错disk by-uuid ########### does not exist dropping to a shell
- 设置SOCKET的发送与接收缓冲区