一个简单的shell

最近按照mit的Operating System Engineering课程(6.828/Fall 2014)学习从零编写一个简单的操作系统。

第一节课的作业1就是写一个简单的shell,能够运行command,并且支持重定向(‘>’, ‘<’)和管道(‘|’),但不支持脚本编程。

课程给的源码已经实现了参数的解析

1 struct cmd * parsecmd(char *s);

该函数根据每行的命令中是否含有’>’/’<’和’|’返回不同的cmd结构:

 1 struct cmd {
 2     int type;
 3 };
 4
 5 struct execcmd {
 6     int type;
 7     char *argv[MAXARGS];
 8 };
 9
10 struct redircmd {
11     int type;
12     struct cmd *cmd;
13     char *file;
14     int mode;
15     int fd;
16 };
17
18 struct pipecmd {
19     int type;
20     struct cmd *left;
21     struct cmd *right;
22 };

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的处理代码:

 1 void
 2 runcmd(struct cmd *cmd)
 3 {
 4   int p[2], r;
 5   struct execcmd *ecmd;
 6   struct pipecmd *pcmd;
 7   struct redircmd *rcmd;
 8
 9   if(cmd == 0)
10     exit(0);
11
12   switch(cmd->type){
13   default:
14     fprintf(stderr, "unknown runcmd\n");
15     exit(-1);
16
17   case ‘ ‘:
18     ecmd = (struct execcmd*)cmd;
19     if(ecmd->argv[0] == 0)
20       exit(0);
21     fprintf(stderr, "exec not implemented\n");
22     // Your code here ...
23     break;
24
25   case ‘>‘:
26   case ‘<‘:
27     rcmd = (struct redircmd*)cmd;
28     fprintf(stderr, "redir not implemented\n");
29     // Your code here ...
30     runcmd(rcmd->cmd);
31     break;
32
33   case ‘|‘:
34     pcmd = (struct pipecmd*)cmd;
35     fprintf(stderr, "pipe not implemented\n");
36     // Your code here ...
37     break;
38   }
39   exit(0);
40 }

第1种,没有重定向也没有管道的单一命令。只需要调用execvp()替换可执行二进制文件即可。

1   case ‘ ‘:
2     ecmd = (struct execcmd*)cmd;
3     if(ecmd->argv[0] == 0)
4       exit(0);
5     if (execvp(ecmd->argv[0], ecmd->argv) == -1) //失败返回-1,成功返回0
6         perror("exec error");
7     break;

效果如下:

1 [email protected]:~/src/6.828$ ./mysh
2 6.828$ ls
3 file1  file2  mysh  sh.c  tags  t.sh
4 6.828$ 

第2种,支持重定向的命令,例如 echo "hello, world!" > txt。这需要借助close()/open()的特性。每次进程调用open()返回的fd总是当前可用的最小fd。因此如果是重定向标准输入,需要先关闭标准输入(fd为0),再打开文件;如果是重定向标准输出,需要先close标准输出(fd为1)。

 1   case ‘>‘:
 2   case ‘<‘:
 3     rcmd = (struct redircmd*)cmd;
 4     if (close(rcmd->fd) < 0) {
 5         perror("close error");
 6         exit(-1);
 7     }
 8     if (rcmd->mode & O_CREAT) { // 如果是重定向输出,还需要新建输出文件
 9         if (open(rcmd->file, rcmd->mode, S_IRUSR | S_IWUSR) < 0) {
10             perror("open file error");
11             exit(-1);
12         }
13     }
14     else if (open(rcmd->file, rcmd->mode) < 0) {
15         perror("open file error");
16         exit(-1);
17     }
18     runcmd(rcmd->cmd);
19     break;

效果如下:

1 6.828$ echo "hello, world!" > txt
2 6.828$ cat txt
3 "hello, world!"
4 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进程才会继续下一次循环。

 1  case ‘|‘:
 2     pcmd = (struct pipecmd*)cmd;
 3     if (pipe(p)) {
 4         perror("create pipe error");
 5         exit(-1);
 6     }
 7     if (fork1() > 0) {
 8         // parent
 9         close(0);
10         close(p[1]);
11         if (dup(p[0]) < 0)
12             perror("dup");
13         runcmd(pcmd->right);
14     }
15     close(1);
16     close(p[0]);
17     if (dup(p[1]) < 0)
18         perror("dup");
19     runcmd(pcmd->left);
20     break;

效果如下:

1 6.828$ ls | sort | uniq | wc
2       7       7      36
3 6.828$

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-26 09:06:01

一个简单的shell的相关文章

一个简单的shell脚本

一个简单的shell脚本 编写 假设我想知道目前系统上有多少人登录,使用who命令可以告诉你现在系统有谁登录: 1.[[email protected] ~]$ who2.KANO tty1 2016-02-15 01:47 (:0)3.KANO pts/0 2016-02-15 01:48 (kelvin) 当然在个人电脑上,所列出来的列表可能只有上面这么短.但是在一些大型.多用户的系统上,所列出的列表就可能很长.这个时候我们可以使用自动计算用户总数.wc是一个字数计算程序,它可以计算出行数(

一个简单 的Shell 显示程序

#!/bin/bash clear declare FirstName Greeting   Greeting="Hello ," echo "" echo "Enter Your First Name:" read FirstName echo "$Greeting $FirstName" 首先   vim  Print  回车 然后   i   进入插入状态 编辑以上代码,Esc 键回车 输入   chmod  711 P

Linux系统学习笔记之 1 一个简单的shell程序

不看笔记,长时间不用自己都忘了,还是得经常看看笔记啊. 一个简单的shell程序 shell结构 1.#!指定执行脚本的shell 2.#注释行 3.命令和控制结构 创建shell程序的步骤 第一步:创建一个包含命令和控制结构的文件 第二步:修改这个文件的权限使它可以执行. 使用chmod u+x 第三步:执行shell sh /test/example.sh Shell变量 变量:是shell传递数据的一种方法,用来代表每个取值的符号名 shell有两类变量:临时变量和永久变量 临时变量是sh

一个简单的shell脚本-----实现虚拟机实验环境的简单配置

亲自写过shell脚本后才发现,这玩意真是太方便了,当你想把一些琐碎的.细节性的小指令一次性来完成时,脚本无疑是最好的选择,方便.快捷,关键是真是懒人必备啊. 由于安装的centos6.5是最小化安装,且是实验环境,即安装在vmware workstations上面,在首次安装完成后,配置完IP地址的相关信息后,就做了一个初始的快照,以便下次实验完后,可以通过快照快速还原系统. 但是由于实验环境的要求,例如防火墙.SELinux.光盘挂载.yum源配置等,都是最常需要修改的选项,并且由于最小化安

一个简单的shell小脚本,批量ping主机ip的存活状态

#!bin/bash #ping host ip #2018年7月17日 #The shell script author xiaolong for hostip in {200..210}; #此处还可以换成 `seq 200 210`: do ip="192.168.1.$hostip" ping -c 1 -W 2 $ip &> /dev/null #/dev/null相当与是一个垃圾站,将错误信息放到里面: if [ "$?" == "

自动化运维:(3)写一个简单的Shell脚本(案例)

一.需求 1.test.sh 脚本执行时候需要添加参数才能执行 参数和功能详情如下: 参数 执行效果 start 启动中... stop 关闭中... restart 重启中... * 脚本帮助信息... 2.参数的数量有限制,只能是1个,多余一个会提示脚本的帮助信息 3.帮助信息使用函数来实现 信息内容:脚本 test.sh 使用方式: test.sh [ start|stop|restart ] 二.知识点分析 1.zonghe.sh 脚本执行时候需要添加参数才能执行 脚本传参.case语句

单片机裸机下写一个自己的shell调试器

该文章是针对于串口通讯过程中快速定义命令而写的,算是我自己的一个通用化的平台,专门用来进行串口调试用,莫要取笑 要处理串口数据首先是要对单片机的串口中断进行处理,我的方法是正确的命令必须要在命令的结尾处同时带有回车和换行,处理过程如下 //串口接收缓冲区 u8 serial_Buffer[SERIAL_MAX_LENGTH] = {0}; //串口接收数据长度 u16 serial_Buffer_Length = 0; static void SerialRecv(u8 ch) { if((se

在Linux下利用替换函数(exec函数家族)写一个简单的myshell

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> void swap(char **p,char **q) { char *tmp=*p; *p=*q; *q=tmp; } int main(int argc,char *argv[]) { pid_t id; while(1){ printf(&

【sehll学习】linux运维一个简单shell脚本监控系统内存

学习shell脚本入门后,慢慢要尝试编写一些脚本练练手,在这先简单的学习写个系统内存的监控. 1.首先先要确定一下截取一下需要关注的内存使用值,可使用free 命令来操作 free -m 显示 一般在监控内存是我们都是截取 第三行(-/+ buffers/cache)的值.确定后可以使用管道线和grep命令来获取这个的值. free -m | grep - | awk  '{print $4}' 获取到他的值为  858 当中 grep -  就是匹配一下要选取的内容,不太熟悉的可以学习一下gr