在unix中,一个shell的执行流程如下:
读入命令
时间轴
------------------------>>
___ ____
sh----| |-------------------| |
^ |__| |___|
| | 新进程 |
| |____>"ls"--->退出 |____>"ps"
"ls"
一个程序如何运行另一个程序?
使用execvp,这个函数有两个参数:要运行的程序名称和这个程序的命令行参数组。当程序运行时命令行参数以argv[]的形式传递给程序. 注意,将数组的第一个元素设置为程序的名称,最后一个元素必须是Null.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0;
printf("***中断这个程序的执行***\n");
execvp("ls", arglist);
printf("ls is done, bye\n");
}
yubo@debian:~/test/tmp/unix/shell$ ./exec
***中断这个程序的执行***
总用量 88
-rwxr-xr-x 1 yubo yubo 6108 5月 18 06:40 exec
-rw-r--r-- 1 yubo yubo 407 5月 18 07:08 exec1.c
-rw-r--r-- 1 yubo yubo 801 5月 16 19:28 execute.c
-rw-r--r-- 1 yubo yubo 29520 5月 16 19:05 segfault
-rwxr-xr-x 1 yubo yubo 13680 5月 16 19:30 smsh
-rw-r--r-- 1 yubo yubo 811 5月 16 19:30 smsh1.c
-rw-r--r-- 1 yubo yubo 389 5月 16 19:09 smsh.h
-rw-r--r-- 1 yubo yubo 2630 5月 16 19:30 splitline.c
-rwxr-xr-x 1 yubo yubo 6188 5月 16 19:49 test
-rw-r--r-- 1 yubo yubo 265 5月 16 19:48 test.c
这里你有没有注意到上面程序中的第二条printf语句消息了。这里书上的解释是:内核将新进程载入到当前进程,替换了当前进程的代码和数据,也就是说,execvp这个函数将“ls”这个命令加入到当前的程序,代替了后面的语句,从而使后面的printf语句消失掉。
下面的代码更深入一步:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 20 /* cmdline args */
#define ARGLEN 100 /* token length */
int execute(char *const arglist[])
{
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
}
char *makestring(char *buf)
{
char *cp;
buf[strlen(buf) - 1] = '\0';
cp = malloc(strlen(buf) + 1);
if (cp == NULL) {
fprintf(stderr, "no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
int main()
{
char *arglist[MAXARGS + 1];
int numargs;
char argbuf[ARGLEN]; /* read stuff from here */
numargs = 0;
while( numargs < MAXARGS) {
printf("Arg[%d]?", numargs);
if (fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n') {
arglist[numargs++] = makestring(argbuf);
}
else {
if (numargs > 0) {
printf("%s", argbuf);
arglist[numargs] = NULL;
if (execute(arglist))
numargs = 0;
}
}
}
return 0;
}
下面是演示:
yubo@debian:~/test/tmp/unix/shell$ ./psh
Arg[0]?ls
Arg[1]?-l
Arg[2]?.
Arg[3]?
总用量 28
-rwxr-xr-x 1 yubo yubo 6108 5月 18 18:01 exec
-rw-r--r-- 1 yubo yubo 413 5月 18 18:01 exec1.c
-rwxr-xr-x 1 yubo yubo 9012 5月 18 19:17 psh
-rw-r--r-- 1 yubo yubo 1116 5月 18 19:17 psh1.c
yubo@debian:~/test/tmp/unix/shell$ ls
exec exec1.c psh psh1.c
这个代码,条理还是比较清楚的。
如何输入连续的shell命令
上面的程序只可运行一次,如果想继续使用,需要再一次运行程序。那么,如何使用真正的shell呢?
答案是fork().
在这篇文章里,我们详细讲解了fork的用法。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define MAXARGS 20
#define ARGLEN 100
void execute(char **); /* execvp */
char *makestring(char *); /* to restore args vector */
int main()
{
char *arglist[MAXARGS + 1];
int numargs = 0;
char argbuf[ARGLEN];
while(numargs < MAXARGS){
printf("Arg[%d]", numargs);
if (fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n')
arglist[numargs++] = makestring(argbuf);
else {
if (numargs > 0) {
arglist[numargs] = NULL;
execute(arglist);
numargs = 0;
}
}
}
return 0;
}
void execute(char *arglist[])
{
int pid, existstatus;
pid = fork(); /* make new process */
switch(pid){
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
default:
while(wait(&existstatus) != pid)
;
printf("child exited with status %d, %d\n",
existstatus >> 8, existstatus &0377);
}
}
char *makestring(char *buf)
{
char *cp;
buf[strlen(buf) - 1] = '\0';
cp = malloc(strlen(buf) + 1);
if (cp == NULL){
fprintf(stderr, "no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
下面是输出结果:
yubo@debian:~/test/tmp/unix/shell$ ./psh
Arg[0]ls
Arg[1]-l
Arg[2].
Arg[3]
总用量 24
-rwxr-xr-x 1 yubo yubo 6108 5月 18 18:01 exec
-rwxr-xr-x 1 yubo yubo 9440 5月 18 23:00 psh
-rw-r--r-- 1 yubo yubo 1336 5月 18 23:00 psh2.c
child exited with status 0, 0
Arg[0]ps
Arg[1]
PID TTY TIME CMD
3393 pts/1 00:00:00 bash
3940 pts/1 00:00:00 psh
3944 pts/1 00:00:00 ps
child exited with status 0, 0
Arg[0]
但是,有一点是比较遗憾的:
./psh
Arg[0]tr
Arg[1][a-z]
Arg[2][A-Z]
Arg[3]
hello
HELLO
now to press
NOW TO PRESS
^C
yubo@debian:~/test/tmp/unix/shell$
针对tr程序(psh的子进程)的Ctrl+C将psh也给停止了,正确的做法是不影响父进程。