MIT 6.828 Assignment:Shell

通过在小shell中实现多个功能,这个assignment将使我更熟悉Unix系统调用接口和shell


MIT 6.828 Operating System Engineering

做作业之前先熟悉OSMIT 6.828 book_xv6:Chapter 0
该作业的解释文档
下载sh.c

建立t.sh文件

1
2
3
4
5
6
ls > y
cat < y | sort | uniq | wc > y1
cat y1
rm y1
ls | sort | uniq | wc
rm y

gcc,你可以编译骨架shell,如下所示:
$ gcc sh.c
生成一个a.out文件,你可以运行:
$ ./a.out <t.sh
此执行将打印错误消息,因为您尚未实现多个功能。在本作业的其余部分中,您将实现这些功能

分析

先看看主函数的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int
main(void)
{
static char buf[100];
int fd, r;

// Read and run input commands.
while(getcmd(buf, sizeof(buf)) >= 0){
if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
// Clumsy but will have to do for now.
// Chdir has no effect on the parent if run in the child.
buf[strlen(buf)-1] = 0; // chop \n
if(chdir(buf+3) < 0)
fprintf(stderr, "cannot cd %s\n", buf+3);
continue;
}
if(fork1() == 0)
runcmd(parsecmd(buf));
wait(&r);
}
exit(0);
}

循环调用getcmd函数读入命令,如果是cd命令,则先切换文件夹,因为如果在子进程切换将对父进程无效。确认不是cd之后,创建子进程并调用关键函数runcmd(),其中parsecmd(buf)无非就是解析命令,里面就是一些字符串处理,不关心。

下面就我们要完成的代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Execute cmd.  Never returns.
void
runcmd(struct cmd *cmd)
{
int p[2], r;
struct execcmd *ecmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;

if(cmd == 0)
exit(0);

switch(cmd->type){
default:
fprintf(stderr, "unknown runcmd\n");
exit(-1);

case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit(0);
fprintf(stderr, "exec not implemented\n");
// Your code here ...
break;

case '>':
case '<':
rcmd = (struct redircmd*)cmd;
fprintf(stderr, "redir not implemented\n");
// Your code here ...
runcmd(rcmd->cmd);
break;

case '|':
pcmd = (struct pipecmd*)cmd;
fprintf(stderr, "pipe not implemented\n");
// Your code here ...
break;
}
exit(0);
}

parsecmd把命令分成了3个类型,分别是可执行命令,重定向命令,以及管道命令。也就是接下来要实现的

简单命令

该题的要求就是先调用当前目录下的可执行程序,如果不存在,就去/bin文件夹下寻找,如果没有则报错

需要使用的就是execv函数,它是exec函数族的一个,exec函数族的作用就是根据pathname找到可执行文件,并用它取代调用进程的内容。虽然pid未改变,但是实际运行的内容已经不同。结合之前main函数中的内容,可以看出Shell执行某个命令实际上就是fork出一个子进程,然后把子进程替换为想要执行的程序。这个函数在小册子中也讲解过,可以通过man 3 exec去查看函数解释

除此之外,还需要一个辅助函数access,它的作用是检查能否对某个文件(pathname)执行某个操作(mode)。模式有:

  • R_OK:测试读许可权
  • W_OK:测试写许可权
  • X_OK:测试执行许可权
  • F_OK:测试文件是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
case ' ':
ecmd = (struct execcmd*)cmd;
if (ecmd->argv[0] == 0)
exit(0);
// fprintf(stderr, "exec not implemented\n");
if (access(ecmd->argv[0], F_OK) == 0) {
execv(ecmd->argv[0], ecmd->argv);
} else {
const char *binPath = "/bin/";
int pathLen = strlen(binPath) + strlen(ecmd->argv[0]);
char *abs_path = (char *)malloc((pathLen+1)*sizeof(char));
strcpy(abs_path, binPath);
strcat(abs_path, ecmd->argv[0]);
if (access(abs_path, F_OK) == 0) {
execv(abs_path, ecmd->argv);
} else {
fprintf(stderr, "%s: Command not found\n", ecmd->argv[0]);
}
}
break;

编译运行截图,嘻嘻嘻!

I/O重定向

The parser already recognizes ">" and "<", and builds a redircmd for you, so your job is just filling out the missing code in runcmd for those symbols. You might find the man pages for open and close useful

1
2
3
4
5
6
7
8
9
10
11
12
case '>':
case '<':
rcmd = (struct redircmd*)cmd;
// fprintf(stderr, "redir not implemented\n");
// Your code here ...
close(rcmd->fd);
if (open(rcmd->file, rcmd->mode, 0644) < 0) {
fprintf(stderr, "Unable to open file: %s\n", rcmd->file);
exit(0);
}
runcmd(rcmd->cmd);
break;

思路就是先关闭程序原先的标准输入/输出,打开指定文件作为新的标准输入/输出。非常容易漏掉权限位,即open的第三个参数

编译运行截图,嘻嘻嘻!

管道

You might find the man pages for pipe, fork, close, and dup useful,也就是说这次涉及的函数主要是pipedupforkclose,通过输入前面创建的t.sh批处理文件,来计算当前目录下文件的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case '|':
pcmd = (struct pipecmd*)cmd;
// fprintf(stderr, "pipe not implemented\n");
// Your code here ...
if (pipe(p) < 0) fprintf(stderr,"pipe failed\n");
if (fork1() == 0) {
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
} else {
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}
break;

由于批处理文件的命令既有在/bin/下,也有在/usr/bin,因此之前的case需要修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit(0);
// fprintf(stderr, "exec not implemented\n");
if(access(ecmd->argv[0], F_OK) == 0) {
execv(ecmd->argv[0], ecmd->argv);
} else {
// 将路径改为数组实现
const char *binPath[] = {"/bin/", "/usr/bin/"};
char *abs_path;
int bin_count = sizeof(binPath)/sizeof(binPath[0]);
int found = 0;
for (int i=0; i<bin_count && found==0; i++) {
int pathLen = strlen(binPath[i]) + strlen(ecmd->argv[0]);
abs_path = (char *)malloc((pathLen+1)*sizeof(char));
strcpy(abs_path, binPath[i]);
strcat(abs_path, ecmd->argv[0]);
if(access(abs_path, F_OK) == 0) {
execv(abs_path, ecmd->argv);
found = 1;
}
free(abs_path);
}
if (found == 0) {
fprintf(stderr, "%s: Command not found\n", ecmd->argv[0]);
}
}

break;

编译运行截图,嘻嘻嘻!