MIT 6.828 Homework: Shell

实验提供了一个shell的简化版本,主要功能是解析shell命令并且执行

在提供的shell代码中已经编写好了对命令进行解析的部分,我们需要的是利用Unix系统调用对命令进行执行

Executing simple commands

任务: 执行简单的命令,如:

$ ls

要完成简单命令的执行,应该补全runcmd函数中“ case ' ' ”部分的代码

查询exec系统调用的man手册:

int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);

p - execlp(), execvp(), execvpe()

These functions duplicate the actions of the shell in searching for an executable file if the specified file‐name does not contain a slash (/) character. The file is sought in the colon-separated list of directory pathnames specified in the PATH environment variable. If this variable isn't defined, the path list defaults to a list that includes the directories returned by confstr(_CS_PATH) (which typically returns the value "/bin:/usr/bin") and possibly also the current working directory; see NOTES for further details.

If the specified filename includes a slash character, then PATH is ignored, and the file at the specified pathname is executed.

综合来看,应该选择exevp()系统调用,接受的参数的包括待执行的文件以及参数列表。其中,*file会在PATH环境变量中搜索,如果含有/,则不在PATH中搜索,而将其视为路径名。这样,无论是类似/bin/ls这样的命令,还是ls这样的命令,都能够成功执行

添加的代码如下:

case ' ':
  ecmd = (struct execcmd*)cmd;
  if(ecmd->argv[0] == 0)
    exit(0);
  if(execvp(ecmd->argv[0], ecmd->argv) == -1)
    fprintf(stderr, "command not found: %s\n", ecmd->argv[0]);
  break;

测试的结果如下:

6.828$ ls
makefile  note.md  sh  sh.c  t.sh  x.txt
6.828$ /bin/ls
makefile  note.md  sh  sh.c  t.sh  x.txt

I/O redirection

任务: 实现 < 和 > 指示的I/O重定向功能

这里要使用open系统调用打开重定向指示的文件,查询man手册可知,open接受两个或三个参数:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname是要打开的文件路径,flags是打开文件所要使用的操作,mode是如果新建文件,文件访问权限的初始值,如果没有新建文件,mode参数会被忽略,参数的具体值详见man手册

这里因为可能需要新建文件,所以使用三个参数的版本:

open(rcmd->file, rcmd->mode, S_IRWXU | S_IRWXG | S_IRWXO)

注意(都是本人踩过的坑):

  1. 在调用open前记得调用close关闭原本的文件描述符rcmd->fd
  2. 关注mode参数的值,避免新建的文件无法被后面的命令打开读取

添加的完整代码如下:

case '>':
case '<':
  rcmd = (struct redircmd*)cmd;
  close(rcmd->fd);
  if(open(rcmd->file, rcmd->mode, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
  {
    fprintf(stderr, "fail to open file %s\n", rcmd->file);
    exit(EXIT_FAILURE);
  }
  runcmd(rcmd->cmd);
  break;

测试的结果如下:

6.828$ echo operating-system > x.txt
6.828$ cat < x.txt
operating-system

Implement pipes

任务: 实现通过管道执行连续的命令

通过Xv6手册的内容结合pipe的man手册的内容可以对管道的使用有一个大概的了解,这里实际上是通过新建一个进程,由父子进程分别执行管道左右两端的命令,通过管道将一侧的命令的输出传递给另一侧命令的输入;这本质上也是一种重定向,将左侧命令的输出重定向,输出到管道的输入端,将右侧命令的输入重定向,从管道的输出端输入

通过man手册查询实验中提示的pipe, fork, close, dup系统调用之后,就可以成功编写出代码了

推荐使用dup2:

int dup2(int oldfd, int newfd);

功能是关闭newfd指向的文件,并让newfd指向oldfd指向的文件,如果newfd和oldfd指向同一个文件,dup2不作任何操作,如果oldfd不是合法的文件描述符,则dup2不会关闭newfd指向的文件,并且系统调用会失败

添加的代码如下:

case '|':
  pcmd = (struct pipecmd*)cmd;
  if(pipe(p) == -1)
  {
    fprintf(stderr, "fail to create a pipe\n");
    exit(EXIT_FAILURE);
  }
  if(fork() == 0)
  {
    dup2(p[1], STDOUT_FILENO);
    runcmd(pcmd->right);
  }
  else
  {
    dup2(p[0], STDIN_FILENO);
    runcmd(pcmd->left);
  }
  break;

测试的结果如下:

6.828$ ls | sort | uniq | wc
makefile  note.md  sh  sh.c  t.sh  x.txt
posted @ 2022-11-11 11:34  雪中的茶  阅读(174)  评论(0编辑  收藏  举报