shell源码地址:sh.c
执行简单命令
找到sh.c中case ' '
处,在此书写执行普通命令的代码。注意到:
struct execcmd {
int type; // ' '
char *argv[MAXARGS]; // arguments to the command to be exec-ed
};
和函数
int execv(const char *path, char *const argv[]);
所以直接使用结构体提供的参数即可:
// fprintf(stderr, "exec not implemented\n");
// Your code here ...
execv(ecmd->argv[0],ecmd->argv);
break;
I/O重定向
I/O重定向的思想就是将标准输入输出(文件描述符为0、1)定向到需输入或输出的文件。代码如下:
//fprintf(stderr, "redir not implemented\n");
// Your code here ...
close(rcmd->fd);
int fd = open(rcmd->file,rcmd->flags,0600);
if (fd<0)
{
fprintf(stderr,"fail to open the file %s\n",rcmd->file);
_exit(1);
}
runcmd(rcmd->cmd);
break;
注意到函数
struct cmd*
redircmd(struct cmd *subcmd, char *file, int type)
{
struct redircmd *cmd;
cmd = malloc(sizeof(*cmd));
memset(cmd, 0, sizeof(*cmd));
cmd->type = type;
cmd->cmd = subcmd;
cmd->file = file;
cmd->flags = (type == '<') ? O_RDONLY : O_WRONLY|O_CREAT|O_TRUNC;
cmd->fd = (type == '<') ? 0 : 1;
return (struct cmd*)cmd;
}
已经为结构体
struct redircmd {
int type; // < or >
struct cmd *cmd; // the command to be run (e.g., an execcmd)
char *file; // the input/output file
int flags; // flags for open() indicating read or write
int fd; // the file descriptor number to use for the file
};
整理好了相关的参数,我们只需要直接使用就行了。
首先,我们将程序的输入或输出文件描述符关掉close(rcmd->fd);
,然后将输入或输出文件描述符重定向到所需要打开的文件int fd = open(rcmd->file,rcmd->flags,0600);
。为什么这样可以实现输入输出重定向到文件呢?因为文件描述符的分配是当前可用的最小文件描述符,当我们关闭了0或1后,那么打开的新的文件,分配得到的一定是0或1,也就是将输入或输出重定向到了该文件。
另外,别忘了做出相应的错误处理,判断打开文件是否成功。
管道
一个管道的两边分别各为一个进程,左边的进程将输出到右边,右边的进程将左边传来的信息作为输入。
预备知识:pipe(),dup(),fork()。详见Chapter 0:Operating System interfaces
实现代码如下:
// fprintf(stderr, "pipe not implemented\n");
// Your code here ...
int p[2];
pipe(p);
if (fork() == 0)//right side
{
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}
else//left side
{
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
}
break;
p[2]数组在两个进程里面都各自有一个备份,子进程和父进程通过这个管道进行通信。子进程运行管道右边的程序,父进程运行管道左边的程序。而p[0]是读出端,p[1]是写入端,所以我们的工作就是将子进程的输入重定向到p[0],将父进程的输出重定向到p[1]。所以上面的代码就不难理解了。
需要注意的地方:当管道没有数据写入的时候(写入端的所有文件描述符都关闭了),管道将会自动关闭。但是这里需要注意一个问题,子进程也完整地继承了p[1],要是子进程中的p[1]不关闭的话,子进程将会同时从管道中读取和写入,当父进程的数据写完了之后,理应关闭管道,但是此时由于子进程的p[1]没有关闭,管道将会陷入无休止的等待当中,不能正常关闭。
测试
将t.sh中的命令的路径补全:
/bin/ls > y
/bin/cat < y | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc > y1
/bin/cat y1
/bin/rm y1
/bin/ls | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc
/bin/rm y
然后运行./run < t.sh
可以得到如下结果:
4 4 16
4 4 16
不同的环境测试结果可能会有所不同。