MIT_6.828_2018_Homework_shell

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

不同的环境测试结果可能会有所不同。

发布了49 篇原创文章 · 获赞 2 · 访问量 7722

猜你喜欢

转载自blog.csdn.net/wysiwygo/article/details/104115708