上节中已经实现了对普通命令的解析,包括输入重定向,输出重定向,管道,后台作业,这次就来执行已经解析好的命令,对应的函数为:execute_command(),首先对带有管道的命令进行执行:
比如:"ls | grep init | wc -w"这条命令,有两条管道,其中最后一条命令是不需要管道的:
【说明】:对于管道的创建,可以参考博文:http://www.cnblogs.com/webor2006/p/3768752.html
另外我们知道,对于创建的管道fds,其中fds[0]表示读端,fds[1]表示写端,会有如下关系:
所以:
所以可以联想到,默认命令的输入是标准输入,输出是标准输出,所以可以在init中对所有命令进行初始化:
接下来,执行命令,这个最初已经实现了,就是fork出一个进程来用execvp函数来进行进程替换既可,这里将命令的执行封装成一个函数:
所以需要在头部声明且实现:
好了,先来编译一下所编写的代码:
很遗憾,木能一次到位,啥问题呢?
所以修改如下:
再次编译:
这次就成功了,另外在运行之前,还需加一个,就是需将3以上的文件描述符全给关掉,因为描述符0、1已经被使用了,之后由于会有重定向一个文件,所以留一个文件描述符2,具体代码如下:
编译运行:
再次编译:
另外,在execute_command命令中,需要关闭命令的描述符:
接下来看下运行效果:
这是因为父进程已经退出了,子进程运行在父进程之后了,所以要解决此问题,则父进程需要等待子进程的执行,修改如下:
定义一个变量来记录最后运行的父进程:
在extends.h中进行声明:
并对变量进行初始化:
当fork一个进程时,则对lastpid进行赋值:
这时,再来看效果:
下面开始解析带输入输出重定向的命令:
另外需要处理一下后台作业的情况:
但是如果是后台作业的话,则会引起僵尸进程(关于什么是僵尸进程,可以参考博文:http://www.cnblogs.com/webor2006/p/3512781.html),所以说需要解决一下:
下面来make一下:
查看man帮助:
再次编译:
下面来看下是否支持输入和输出重定向:
下面采用输入重定向来输出同样的效果:
下面来看下输出重定向:
可见,现在已经支持输入输出重定向了,下面还需看一种异常情况:
当输入不带参数的cat命令时,表是从键盘获取输入,当我们按下ctrl+c时,居然打印出了两个[myshell]$,这个有异常了,我们知道,ctrl+c是向当前进程发送sigint信号,由于我们在setup()已经注册了sigint信号,而且行为为打印[myshell]$
而由于父进程和子进程都能收到sigint信号,因为sigint是向进程组发送的,所以组里面的所有子进程都能收到,所以要解决此问题,需要做如下操作:
因为在后台作业时,已经忽略SIGINT信号了,所以如果是前台作业,则需要恢复,编译再来看效果:
这时为啥呢?这个可能是进程组的关系(关于进程组的概念,可以参考博文:http://www.cnblogs.com/webor2006/p/3514552.html),可以查看一下关系:
但是进入我们自己的shell来查看一下进程组关系:
所以,SIGINT发送给进程组26945时,也就发送了该进程的父进程,因为当前该父进程为进程组,而子进程26946同样也会收到SIGINT信号,所以就打印了两次。
这就涉及到会话期的概念,其中创建新的会话期可以通以以下函数:
所以,在第一个命令fork时,则将这个进程做为进程组组长既可,做法如下:
所以,函数的参数应该发生变化:
然后在子进程中做一个判断,创建新的会话期:
这次再来编译下:
这时再来看一下刚才的问题是否还存在?
这时按下ctrl+c就没有出现两个[myshell]$了,这是为什么呢?
因为ctrl+c是将SIGINT信号发送给当前进程组,也就是对应于上面的27278,那么该进程组下面的所有进程都会收到该信号,由于在前台进程时,将SIGINT信号还原成了默认值:
所以,这时ppid父进程是不会收到该信号的,因为该信号只会发送给当前进程组成里面的所有进程,所以这次就只会打印一次了。
接下来,我们来看一下后台作业:
这里来看,后台作业有问题,由于这里并没有实现作业控制(bg,fg),所以先屏蔽后台作业,等之后有时间再来研究,如下:
这时再来看下之前的bug是否还存在?
看样子还是有问题,还没有屏蔽成功,在屏蔽之前,先来解决一个很明显的bug:
也就是当cmd_count=0时,则没反应了,这时应该做一个容错处理,当为0时不应该执行命令:
这时再来看效果:
下面再来解决屏蔽后台作业的bug,该bug就是如果先敲了一个后台作业命令,之后再执行一个简单命令就会卡住,这是为什么呢,原因其实比较简单:
这时再来看效果:
这样这个bug就成功被解决,另外我们来看下真实的后台作业的输出是怎么样的:
所以,我们也可以给打印一下当前的pid,虽说后台作业的功能没有完全实现:
好了,先学到这,下节见~

linux系统编程综合练习-实现一个小型的shell程序(三)的更多相关文章

  1. linux系统编程综合练习-实现一个小型的shell程序(四)

    上节中已经对后台作业进行了简单处理,基本上要实现的功能已经完了,下面回过头来,对代码进行一个调整,把写得不好的地方梳理一下,给代码加入适当的注释,这种习惯其实是比较好了,由于在开发的时候时间都比较紧, ...

  2. linux系统编程综合练习-实现一个小型的shell程序(一)

    之前已经花了不少篇幅学习了linux系统编程的很多知识点:文件与io.进程.信号.管道,而零散的知识点,怎么能够综合的串接起来是学习的一个很重要的目的,当然最好的方式就是用所学的知识点做一个项目了,所 ...

  3. linux系统编程综合练习-实现一个小型的shell程序(二)

    上节minishell当中,已经初步实现了一个简单命令的解析,这节来继续对更加复杂命令进行解析,包含:输入重定向的解析.管道行的解析.输出重定向的解析以及是否有后台作业的解析,如下: 下面对其进行实现 ...

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

    不看笔记,长时间不用自己都忘了,还是得经常看看笔记啊. 一个简单的shell程序 shell结构 1.#!指定执行脚本的shell 2.#注释行 3.命令和控制结构 创建shell程序的步骤 第一步: ...

  5. Linux系统编程(33)—— socket编程之TCP程序的错误处理

    上一篇的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息. 为使错误处理的代码不影 ...

  6. linux系统编程之进程(一)

    今天起,开始学习linux系统编程中的另一个新的知识点----进程,在学习进程之前,有很多关于进程的概念需要了解,但是,概念是很枯燥的,也是让人很容易迷糊的,所以,先抛开这些抽象的概念,以实际编码来熟 ...

  7. linux系统编程之管道(三)

    今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...

  8. linux系统编程之信号(七)

    今天继续学习信号,主要是学习关于时间和定时器相关的函数的使用,关于这个实际上有很多内容,这里先简要进行说明,等之后再慢慢进行相关深入,也主要是为接下来要做的一个综合linux系统编程的例子做准备,好了 ...

  9. Linux系统编程【2】——编写who命令

    学到的知识点 通过实现who命令,学到了: 1.使用man命令寻找相关信息 2.基于文件编程 3.体会到c库函数与系统调用的不同 4.加深对缓冲技术的理解 who命令的作用 who命令的使用 在控制终 ...

随机推荐

  1. 西门子PLC1200内使用SCL实现简化版PID算法

    西门子自带的PID效果很好,但是会比较吃性能,使用次数有限,很多地方需要PID但不需要这么精准的PID,所以网上找个简单的算法自己调用. 新建数据类型 前三个就是PID三个参数 新建FC块: #PID ...

  2. VS2010 C#添加水晶报表及设计

    添加并设计水晶报表. [1]在VS中添加水晶报表 右击项目-->添加-->组件-->Crystal Report,输入文件名称“CrystallistReport”,如下图所示: 此 ...

  3. leetocode 207 课程表

    解题思路: 本题可约化为:课程安排图是否是 有向无环图(DAG).即课程间规定了前置条件,但不能构成任何环路,否则课程前置条件将不成立. 思路是通过 拓扑排序 判断此课程安排图是否是 有向无环图(DA ...

  4. 【剑指offer】面试题 19. 正则表达式匹配

    面试题 19. 正则表达式匹配

  5. 100天搞定机器学习|Day55 最大熵模型

    1.熵的定义 熵最早是一个物理学概念,由克劳修斯于1854年提出,它是描述事物无序性的参数,跟热力学第二定律的宏观方向性有关:在不加外力的情况下,总是往混乱状态改变.熵增是宇宙的基本定律,自然的有序状 ...

  6. day29——socket套接字(少量不全)

    day29 socket套接字 socket是处于应用层与传输层之间的抽象层,他是一组操作起来非常简单的接口(接受数据)此接口接受数据之后,交由操作系统. 为什么存在socket抽象层? 如果直接与操 ...

  7. vue3生命周期

    介绍 vue3的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用 示例 import { onMounted, onUpdated } from '@vue/compositi ...

  8. [CF896C]Willem, Chtholly and Seniorious

    题目大意:有$n$个数,有$m$次$4$种操作: l r x :将$[l,r]$区间所有数加上$x$ l r x :将$[l,r]$区间所有数变成$x$ l r k :输出$[l,r]$区间第$k$大 ...

  9. uni-app项目导入第三方组件库muse-ui

    你说uni-app是什么 我说,uni-app是一套基于vue.js开发跨平台应用的前端框架,可编译多个平台,比如:Android.IOS.H5.微信小程序.支付宝小程序.头条小程序.百度小程序 懂行 ...

  10. 【转】Redis 基础操作和命令

    笔记 Redis提供了六种基本的数据结构:String,Hash,List,Set,Sorted Set,HyperLogLog. Redis的特点:纯内存操作,单线程工作模型,非阻塞I/O多路复用. ...