实现一个简单的shell
使用已学习的各种C函数实现一个简单的交互式Shell,要求:
1、给出提示符,让用户输入一行命令,识别程序名和参数并调用适当的exec函数执行程序,待执行完成后再次给出提示符。
2、该程序可识别和处理以下符号:
1) 简单的标准输入输出重定向:仿照例 "父子进程ls | wc -l",先dup2然后exec。
2) 管道(|):Shell进程先调用pipe创建管道,然后fork出两个子进程。一个子进程关闭读端,调用dup2将写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而Shell进程把管道的两端都关闭,调用wait等待两个子进程终止。
实现步骤:
1. 接收用户输入命令字符串,拆分命令及参数存储。(自行设计数据存储结构)
2. 实现普通命令加载功能
3. 实现输入、输出重定向的功能
4. 实现管道
5. 支持多重管道
以上。
出于简单,我假设我们输入的命令字符串是符合要求,没有错误的。
我们要实现的有:普通命令;输入输出重定向;单个管道。有四种情况:①ls -ahl 单个命令;②ls -alh > a.txt 输出重定向;③ls -ahl | grep root 管道;④cat < a.txt输出重定向。其实更具体细分还有命令带参数和不带参数的情况。情况有这几种,我们应该用标志将他们区分,所以,储存命令的数据结构就很重要了。这是我设计的结构体:
typedef
struct
My_order
{
char *argv[32]; //命令以及参数、文件
int pipe;
int right;
int left;
} My_order;
我将其命名为My_order。现在我们要做的事是解析用户输入的命令字符串:ls -ahl | grep root 。理想情况下,我们应该将其拆分为 ls 、-ahl、|、grep、root这些字符串。该怎么拆分呢?观察命令字符串:命令参数之间用空格隔开的,我们可以利用这个特性。但是我们要自己造轮子么?不用,C库函数为我们提供了一个字符串分割函数strtok():
原型:char *strtok(char *restrict s1,const char * restrict s2);
描述:该函数把s1字符串分解为单独的记号。s2字符串包含了作为记号分隔符的字符。按顺序调用该函数。第一次调用时,s1应指向待分解的字符串。函数定位到非分隔符后的第一个记号分隔符,并用空字符替换它。函数返回一个指针,指向存储第一个记号的字符串。若未找到,返回NULL。再次调用strtok查找字符串中的更多记号。每次调用都返回指向下一个记号的指针。未找到返回NULL。
于是,我们像下面这样调用该函数就可以完美的解决问题了。
int resolve_order(My_order *my_order, char p[])
{
//先初始化
my_order->pipe = my_order->left = my_order->right = 0;
for (int i = 0; i != 32; i++)
{
my_order->argv[i] = NULL;
}
int i = 0;
int option = 0;
my_order->argv[i] = strtok(p, " ");
while (my_order->argv[++i] = strtok(NULL, " "))
{
if (strcmp(my_order->argv[i], " | ") == 0)
{
my_order->pipe++;
}
else
if (strcmp(my_order->argv[i], ">") == 0)
{
my_order->right++;
}
else
if (strcmp(my_order->argv[i], "<") == 0)
{
my_order->left++;
}
}
return 0;
}
当命令字符串中有管道,输入输出重定向符的时候,相应的值就要增加。但是最多也只能是1,再多的话,我这个简单的shell就不能胜任了。即像这样的命令:cat|cat|cat我是解决不了的。
我们上面的示例命令有管道,所以我们要用到pipe函数,建立管道,使进程之间能够相互通讯。但是我们第一步是要创建进程,不多,一个就够了,使用fork()函数。但是在此之前我们还有问题要解决:是子进程解决管道前面的命令呢还是父进程先解决?子进程和父进程谁先执行?这里废话一点:以前有个牛人(抱歉不记得是谁了,若是知道请告知)做了个实验:观察父子进程谁先被执行,最后得出的结论是绝大部分情况下是父进程先抢到CPU资源。但是这并没有理论支撑。计算机科学没有理论来支持这个结论。(当故事听就好哈,不要较真,本人还是萌新。)虽然有大牛得出这样的结论来了,但是我还是没有遵循这个结论。^_^。所以我让子进程去执行管道前面的命令了, 哎。还好我写了这个博客,不然我会闹大笑话。必须要两个子进程,一个不行,除非我就执行这一个管道命令。exec族函数的一大特点是什么?执行完成指定程序之后根本就不回来!意味着这个进程死掉了,无论是父进程还是子进程都会被回收掉。所以还是要两个子进程,这里要注意的是,使用兄弟进程进行通讯的时候父进程应该使用waitpid函数进行非阻塞回收。但是在我的实现上依旧有那种阻塞情况发生,是在是不懂怎么回事。不过这不重要。(玛德,废话真多。)代码:
void my_pipe(My_order *my_order)
{
int fd[2];
int p_ret = pipe(fd);//fd[0]->r;fd[1]->w
if (-1 == p_ret)
{
perror("pipe error ");
exit(1);
}
int i = 0;
int pid;
for (; i != 2; i++)
{
if (!(pid = fork()))
{
break;
}
}
if (0 == i)
{
if (strlen(my_order->argv[1]) > 1)
{
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp(my_order->argv[3], my_order->argv[3], my_order->argv[4], NULL);
}
else
{
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp(my_order->argv[2], my_order->argv[2], my_order->argv[3], NULL);
}
}
else
if (1 == i)
{
if (strlen(my_order->argv[1]) > 1)//有参数
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp(my_order->argv[0], my_order->argv[0], my_order->argv[1], NULL);
}
else
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp(my_order->argv[0], my_order->argv[0], NULL);
}
}
else
{
waitpid(-1, NULL, WNOHANG);
waitpid(-1, NULL, WNOHANG);
}
return 0;
}
我写的不够严谨,都没有什么错误检查。别像我这样写,要检查错误,检查函数返回值。
比如就是万一有用户这样写 ls -alh | a.txt 虽然这样在真正的shell也不能通过,但是别人有错误提示啊。
其实有管道这个是整个程序中最难的部分。接下来的重定向其实很简单的。进过我的测试(用我那点可怜的知识)发现,重定向无非三种正确(的简单的)情况:命令>命令;命令>文件;命令<文件。前面的部分全是命令,后面的就稍微有点不同。那么问题来了:如何判断后面的是文件还是命令?以有无后缀区分?但是在Linux中后缀是方便我们识别的而不是系统的刚需啊。我也经常看到gcc main.c -o a这样的命令啊。(别喷别喷)没事,大部分的Linux命令都在/bin目录下呢。简单的实现也无需考虑那么多,现在就是我们需要去查看目录中有无对应字符串内容的命令。读目录也很简单啊,我的博客前几篇(忘了哪一篇了)介绍了读取指定目录获取文件数目内容。我们稍微变换一下就可以用来区分文件or命令了:
int get_dirfile(char *name) //命令存在返回0;不存在返回-1;
{
DIR *dir = opendir(" / bin");
struct
dirent *di;
while ((di = readdir(dir)) != NULL)
{
if (strcmp(di->d_name, name) == 0)
{
return 0;
break;
}
}
return -1;
}
//是命令就执行。是文件就打开(创建)。打开文件也很简单嘛:
int open_file(char
p[])
{
int o_ret = open(p, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (o_ret == -1)
{
perror("open file error ");
exit(1);
}
return o_ret;
}
相关的函数、宏若不知道意思,请参阅前几篇(也忘了是哪一篇了)的介绍。
接下来,就要解决重定向了。dup2函数一定是需要的(我也在前几篇介绍了的),这里就不介绍了。接下来就很简单了,就是每个命令就要确定一下参数有无。
int exec_order(My_order *my_order)
{
if ((my_order->pipe == 0) && (my_order->left == 0) && (my_order->right == 0))
{
if (!fork())
{
if (my_order->argv[1] != NULL)
execlp(my_order->argv[0], my_order->argv[0], my_order->argv[1], NULL);
else
execlp(my_order->argv[0], my_order->argv[0], NULL);
}
else
{
wait(NULL);
}
}
else
if ((my_order->pipe == 1) && (my_order->left == 0) && (my_order->right == 0))
{
my_pipe(my_order);
}
else
if ((my_order->pipe == 0) && (my_order->left == 1) && (my_order->right == 0))
{
if (!fork())
{
execlp(my_order->argv[0], my_order->argv[0], my_order->argv[2], NULL);
}
else
{
wait(NULL);
}
}
else
if ((my_order->pipe == 0) && (my_order->left == 0) && (my_order->right == 1))
{
if (!fork())
{
if (strlen(my_order->argv[1]) > 1)
{
int fd = open_file(my_order->argv[3]);
dup2(fd, STDOUT_FILENO);//执行之后,标准输入就指向了fd
execlp(my_order->argv[0], my_order->argv[0], my_order->argv[1], NULL);
close(fd);
}
else
{
int fd = open_file(my_order->argv[2]);
dup2(fd, STDOUT_FILENO);
execlp(my_order->argv[0], my_order->argv[0], NULL);
close(fd);
}
}
else
{
wait(NULL);
}
}
}
其实这里有个小问题,就是像ps这样的命令参数是没有-的,直接就是ps a这样。为了简便,先这样吧。
main函数就很简单了。
int main(void)
{
while (1)
{
My_order my_order;
char p[32] = { '\0' };
puts("GYJ_LoveDanDan@desktop:—————————————— - ");
gets(p);
//char p[8] = { "ls -alh | grep lovedan " };
resolve_order(&my_order, p);
exec_order(&my_order);
}
return 0;
}
写个这程序,真的是,感觉到了自己真是菜鸡。最开始的任务其实有这个:
你的程序应该可以处理以下命令:
○ls△-l△-R○>○file1○
○cat○<○file1○|○wc△-c○>○file1○
注:○表示零个或多个空格,△表示一个或多个空格
5. 支持多重管道:类似于cat|cat|cat
我最开始为了解析字符串,操碎了心,眼看就要成功了,但是因为我用来储存的数据结构不好用于执行execlp函数,就放弃了,几经波折,我看透了。自己砍了要求,很勉强的实现了这个四不像shell。我想问人,没人回答我,我想查资料,没找到。这也许就是小说中散修和宗门的区别吧。
实现一个简单的shell的更多相关文章
- 如何写一个简单的shell
如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...
- 一个简单的shell脚本
一个简单的shell脚本 一个简单的shell脚本 编写 假设我想知道目前系统上有多少人登录,使用who命令可以告诉你现在系统有谁登录: 1.[KANO@kelvin ~]$ who2.KANO tt ...
- Linux系统学习笔记之 1 一个简单的shell程序
不看笔记,长时间不用自己都忘了,还是得经常看看笔记啊. 一个简单的shell程序 shell结构 1.#!指定执行脚本的shell 2.#注释行 3.命令和控制结构 创建shell程序的步骤 第一步: ...
- python定义的一个简单的shell函数的代码
把写代码过程中经常用到的一些代码段做个记录,如下代码段是关于python定义的一个简单的shell函数的代码. pipe = subprocess.Popen(cmd, stdout=subproce ...
- Linux——模拟实现一个简单的shell(带重定向)
进程的相关知识是操作系统一个重要的模块.在理解进程概念同时,还需了解如何控制进程.对于进程控制,通常分成1.进程创建 (fork函数) 2.进程等待(wait系列) 3.进程替换(exec系列) 4 ...
- UNIX-LINUX编程实践教程->第八章->实例代码注解->写一个简单的shell
一 分析 要实现一个shell,需包含3个步骤 1)读入指令 2)指令解析 3)执行指令 1 从键盘读入指令 从键盘读入指令的几个要点: 1)调用getc函数等待并获取用户键盘输入. 2)每一行命令的 ...
- 如何在linux下编写一个简单的Shell脚本程序
在了解了linux终端和其搭配的基本Shell(默认为bash)的基础下,我们就可以在终端中用vi/vim编辑器编写一个shell的脚本程序了 Shell既为一种命令解释解释工具,又是一种脚本编程语言 ...
- 一个简单的Shell脚本(解决windows上文本在macos上乱码问题)
之所以有这一篇文章,是因为之前我写过的一篇文章:“解决Mac上打开txt文件乱码问题”:传送门: https://www.cnblogs.com/chester-cs/p/11784079.html ...
- 工作中一个简单的shell程序
下面是工作中用到的链接数据库的shell程序. #!/bin/bash ] ; then echo "prase is wrong ,please check first" exi ...
随机推荐
- Android通过包名启动其他应用,若该应用已启动,则直接将应用切到前台
CommUtil.startActivityForPackage(mContext, "com.autonavi.minimap");//打开高德 CommUtil.java /* ...
- SQL-sqlHelper001
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.D ...
- ArcGIS for android添加图层几何体
GraphicLayer lyr; Map<String,Object> attr = new HashMap<String,Object>(); attr.put(this. ...
- xgboost的sklearn接口和原生接口参数详细说明及调参指点
from xgboost import XGBClassifier XGBClassifier(max_depth=3,learning_rate=0.1,n_estimators=100,silen ...
- GIT命令行笔记
一次常规的初始化+推送: git initgit config user.email "you@example.com"git config user.name "asm ...
- 09 shell脚本程序练习
练习1:写一个脚本程序,完成以下任务 1.添加五个用户,user1.user2.user3.user4.user5: 2.添加之前先判断是否存在该用户,如果存在则显示“某某用户已存在”: 3.每个用户 ...
- PHP unset()函数销毁变量
<?php // 销毁单个变量 unset ($var); // 销毁单个数组元素 unset ($arr['a']); // 销毁多个变量 unset ($var1, $var2, $var3 ...
- 微信小程序支付签名老是失败,在官网的校验签名工具校验成功,老是返回签名失败
在网上也百度了各种签名不正确的解决方法,都没有问题,但签名就是不成功,实在找不出问题了,我就重置了一下api秘钥,结果成功了…… 不知道什么原因第一次填写的api秘钥也是我重置的,填写的也没有问题,但 ...
- Java - 28 Java 泛型
Java 泛型 如果我们只写一个排序方法,就能够对整型数组.字符串数组甚至支持排序的任何类型的数组进行排序,这该多好啊. Java泛型方法和泛型类支持程序员使用一个方法指定一组相关方法,或者使用一个类 ...
- How The Kernel Manages Your Memory.内核是如何管理内存的
原文标题:How The Kernel Manages Your Memory 原文地址:http://duartes.org/gustavo/blog/ [注:本人水平有限,只好挑一些国外高手的精彩 ...