一个简单的shell程序
来源:互联网 发布:计算机二级c语言真题 编辑:程序博客网 时间:2024/06/05 18:16
最近按照mit的Operating System Engineering课程(6.828/Fall 2014)学习从零编写一个简单的操作系统。
第一节课的作业1就是写一个简单的shell,能够运行command,并且支持重定向(‘>’, ‘<’)和管道(‘|’),但不支持脚本编程。
课程给的源码已经实现了参数的解析
struct cmd * parsecmd(char *s);
该函数根据每行的命令中是否含有’>’/’<’和’|’返回不同的cmd结构:
struct cmd { int type;};struct execcmd { int type; char *argv[MAXARGS];};struct redircmd { int type; struct cmd *cmd; char *file; int mode; int fd;};struct pipecmd { int type; struct cmd *left; struct cmd *right;};
C语言虽然天生不支持面向对象技术,但是通过仔细的指针操作仍然能够达到一定的类似于面向对象的效果。
parsecmd()函数会创建3种cmd:execcmd, pipecmd或redircmd。但是如何返回能够让调用者识别出是哪一种cmd呢?注意到3种cmd的第一个成员变量都是int type
,并且3种cmd的type值不一样(取值分别为’ ‘, ‘>’/’<’, ‘|’),这是故意为之的。parsecmd会将这3种cmd的指针(指向具体对象的指针)强制转化为struct cmd*
(指向基类的指针),然后调用者根据type值来确定是哪种具体的cmd,再将之强制转换回去。
而作业的内容就是在runcmd()
中实现3种cmd的处理代码:
voidruncmd(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);}
第1种,没有重定向也没有管道的单一命令。只需要调用execvp()
替换可执行二进制文件即可。
case ' ': ecmd = (struct execcmd*)cmd; if(ecmd->argv[0] == 0) exit(0); if (execvp(ecmd->argv[0], ecmd->argv) == -1) //失败返回-1,成功返回0 perror("exec error"); break;
效果如下:
bookxiao@ubuntu-kernel:~/src/6.828$ ./mysh 6.828$ lsfile1 file2 mysh sh.c tags t.sh6.828$
第2种,支持重定向的命令,例如 echo "hello, world!" > txt
。这需要借助close()/open()
的特性。每次进程调用open()返回的fd总是当前可用的最小fd。因此如果是重定向标准输入,需要先关闭标准输入(fd为0),再打开文件;如果是重定向标准输出,需要先close标准输出(fd为1)。
case '>': case '<': rcmd = (struct redircmd*)cmd; if (close(rcmd->fd) < 0) { perror("close error"); exit(-1); } if (rcmd->mode & O_CREAT) { // 如果是重定向输出,还需要新建输出文件 if (open(rcmd->file, rcmd->mode, S_IRUSR | S_IWUSR) < 0) { perror("open file error"); exit(-1); } } else if (open(rcmd->file, rcmd->mode) < 0) { perror("open file error"); exit(-1); } runcmd(rcmd->cmd); break;
效果如下:
6.828$ echo "hello, world!" > txt6.828$ cat txt"hello, world!"6.828$
第3种,支持管道的命令,例如ls | sort | uniq | wc
。这需要fork新的进程来处理不同的cmd,并且需要借助pipe()来实现父子进程间通信。这里需要注意的是,是由父进程执行第一个命令还是由子进程执行第一个命令。在main()
中父进程p0循环读标准输入,每读入一行就fork一个子进程p1来处理命令并等待子进程p2退出后继续循环。
因此,在这里,如果由父进程p1执行ls
命令的话,它将数据写入pipe后就会退出,这样就会造成这种效果:main进程已经等待到p1进程退出了,就会打印出6.828$
来接受新的输入,但是p2进程还没有执行完,它的输出就会在main进程输出6.828$
之后了。所以,需要由子进程p2来执行第一个命令,接着如果仍然有管道,则由p3子进程来执行第二命令,最后p1进程来执行最后一个命令。只有等p1执行完最后一个命令退出后,main进程才会继续下一次循环。
case '|': pcmd = (struct pipecmd*)cmd; if (pipe(p)) { perror("create pipe error"); exit(-1); } if (fork1() > 0) { // parent close(0); close(p[1]); if (dup(p[0]) < 0) perror("dup"); runcmd(pcmd->right); } close(1); close(p[0]); if (dup(p[1]) < 0) perror("dup"); runcmd(pcmd->left); break;
效果如下:
6.828$ ls | sort | uniq | wc 7 7 366.828$
- 一个简单的shell程序
- 一个简单的模拟shell的程序
- 一个简单 的Shell 显示程序
- 一个简单的auth shell程序
- 一个最简单的shell程序
- shell学习笔记(2) 一个简单的shell脚本程序
- linux系统-shell编程-一个简单的shell程序
- 简单的shell程序
- 一个简单的shell脚本编写的GUI程序
- 一个简单的shell
- 一个简单的shell
- 自己动手写一个简单的Shell之二:运行程序
- 一个简单的linux shell程序示例及其说明
- 自己动手写一个简单的Windows shell扩展程序
- 一个简单的shell脚本程序(运用expr命令)
- 一个简单的linux shell判断小程序
- shell下一个简单的程序
- 编辑简单的 shell程序
- Ant问题
- MATLAB常用数学函数
- 【解惑】领略内部类的“内部”
- iOS开发中@2x等后缀的图片干嘛用的
- Android 界面—UI 开发控件
- 一个简单的shell程序
- 关于UIView的autoresizingMask属性的研究
- android自动弹出软键盘(输入键盘)
- struts2标签详解
- IOS数据存储方式(NSUserDefaults)
- 由浅入深学习自定义控件(1)-给控件添加自定义属性
- JSON.parse()和JSON.stringify()
- java学习日记(非技术贴)
- redis连接远程服务器