上节minishell当中,已经初步实现了一个简单命令的解析,这节来继续对更加复杂命令进行解析,包含:输入重定向的解析、管道行的解析、输出重定向的解析以及是否有后台作业的解析,如下:
下面对其进行实现,上节中实现了对单条命令的解析,如下:
这节因为是多条,所以解析命令的实现也得重新开始写,在写之前,先列一个实现步骤:
先写一个流程伪代码,交其框架定出来,然后再去实现一个个功能函数,最后整个功能完成,这是一个比较好的编码习惯,先全局,先局部:
下面先定义未实现的函数:
 
然后再定义用到的全局变量:
并且在extends.h文件中进行声明:
这时,先来make一下,看这些修改能否正常编译:
从中来看,目前这个简单命令的解析框架已经搭建完毕,接下来,则是一个个函数进行实现:
void get_command(int i):获取第几条命令:
在实现解析方法之前,需要重新定义一下我们的命令数据结构,因为这一次是由多个命令组成,而不是单个命令,那要定义成一个什么样的结构呢?
所以,我们的命令数据结构需调整为:
由于现在是多条命令解析,所以cmd需要将其声明为数组:
另外,对于是cmd的初始化操作也得进行变化:
在继续编写前,咱们先来编译一下,看是否能编译过,一步一步脚印,步步为营,这样编写能减少出错的机率:
出错了,这也说明好的编码习惯,得改一点,立马来确认是否能过编译通过,一点点往上加功能,这样也会比较踏实,好了解决错误,是由于在execute_command还是执行的单条命令,所以肯定会出错,先将其注释掉:
再来编译这次就ok了,下面开始进行解析,根据解析的示例图,需要将cmdline中的命令参数提取到avline数组中,所以声明两个变量来分别指向cmdline和avline:
下面开始一步步进行解析:
 
也就是这一步:
其实,这个解析还是有点问题,比如命令"cat < test.txt",依照上面编写的代码来分析,当解析完cat之后,因为遇到了' ',所以j++:
再次循环:
也就是cmd[i].args[1] = ' ';而实际上只有一个cat命令,并没有第二个参数,所以需要做如下处理:
/*
* 解析简单命令至cmd[i]
* 提取cmdline中的命令参数到avline数组中
* 并且将COMMAND结构中的args[]中的每个指针指向这些字符串
*/
void get_command(int i){
/* cat < test.txt | grep -n public > test2.txt & */
int j = ;//代表命令中的参数个数
int inword;//是否在单词中
while(*lineptr != '\0'){
/* 去除空格 */
while(*lineptr == ' ' || *lineptr == '\t')
lineptr++;
     /* 将第i条命令第j个参数指向avptr */
cmd[i].args[j] = avptr;
     /* 提取参数 */
while(*lineptr != '\0'
&& *lineptr != ' '
&& *lineptr != '\t'
&& *lineptr != '>'
&& *lineptr != '<'
&& *lineptr != '|'
&& *lineptr != '&'
&& *lineptr != '\n'){
       /* 参数提取至avptr指针指向的数组 */
*avptr++ = *lineptr++;
inword = 1;
}
*avptr++ = '\0';
switch (*lineptr)//说明命令中的一个单词解析完
{
case ' ':
case '\t'://表示一个命令的第一个参数已经解析完,应该继续解析其第二个参数,如:grep -n public,当解析完grep后,则会遇到空格,应该继续解析参数-n
inword = 0;
j++;
break;
case '<':
case '>':
case '|':
case '&':
case '\n'://说明命令解析结束了
if(inword == 0)
cmd[i].args[j] = NULL;
//这时需要将空格参数去掉return;
default: /* for '\0' */
return;
}
}
}

这时,写了这么多代码,先编译一下:

可见代码现在没有什么问题,接下来实现check函数

int check(const char *str):判断命令是否包含某个字符:
 
怎么来理解上面的写法呢?
理解起来有点绕,可以好好消化下,接下来实现另外一个函数
void getname(char *name):获得重定向后的文件名:
另外,在parse_command()中,需要修改一个地方,就是对于输出重定向,可以有>>,表示追加的方式,所以改变如下:
需要定义一下append全局变量:
对于现在的代码,得验证其是否是正确的,所以,在程序还没有写完之前,可以写一个函数来打印解析的值进行验证程序的合法性,如下:
具体代码实现,比较简单,这里就不多解释了:
运行来验证下:
但是,如果直接回车,看结果:
所以,在init方法中,需将全局变量全部进行一次初始化:
这时再来看:
这时,应该是代码逻辑的问题,来看下代码parse_command()确实是有问题:
怎么修复此问题呢?其实在解析命令最后前,做一个判断既可:
这时,再来试验:
好了,先做到这,下节继续完善~

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

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

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

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

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

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

    上节中已经实现了对普通命令的解析,包括输入重定向,输出重定向,管道,后台作业,这次就来执行已经解析好的命令,对应的函数为:execute_command(),首先对带有管道的命令进行执行: 比如:&q ...

  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. [LeetCode] 323. Number of Connected Components in an Undirected Graph 无向图中的连通区域的个数

    Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), ...

  2. LeetCode 566. 重塑矩阵(Reshape the Matrix)

    566. 重塑矩阵 566. Reshape the Matrix 题目描述 LeetCode LeetCode LeetCode566. Reshape the Matrix简单 Java 实现 c ...

  3. springmvc数据的封装

    spring封装是进行orm封装,可以进行定义数据类型,数据名与接收名相同,进行接收,或者定义类,类的属性名与接收名相同 单个数据类型如图下: 对象类型封装: 其他:乱码处理 在中文字符乱码,需要规定 ...

  4. Java基础笔试练习(八)

    1. 以下关于构造函数的描述错误的是 ( ) A.每个类有且只能有一个构造函数. B.构造函数是类的一种特殊函数,它的方法名必须与类名相同 C.构造函数的主要作用是完成对类的对象的初始化工作 D.一般 ...

  5. 题解 P3957 【跳房子】

    对于这题有一个不用单调队列并且不是玄学设置区间最大值的做法 这题校内模拟考的时候打二分+枚举,结果写炸了,跑过来看题解发现为什么他们的区间最大值都是 $ 1005 $ ???特别懵,其实我的代码在dp ...

  6. Linux06 文件的打包和压缩(gzip/gunzip、tar、bzip2)

    一.gzip/gunzip 这是用于压缩和解压单个文件的工具,且使用方法比较简单 gzip  文件名 gunzip  文件名 二.tar(用的比较多) 不仅可以用于打包文件,还可以将整个目录中的全部文 ...

  7. Delphi百度语音【支持语音识别和语音合成】

    作者QQ:(648437169) 点击下载➨百度语音         语音识别api文档         语音合成api文档 [Delphi 百度语音]支持获取 Access Token.语音识别.语 ...

  8. 【IDEA使用技巧】(5) —— IntelliJ IDEA集成Tomcat部署Maven Web项目

    1.IntelliJ IDEA集成Tomcat部署Maven Web项目 1.1.IDEA构建Maven Web项目 使用IDEA来创建一个简单的Hello World的Maven Web项目,并使用 ...

  9. Mysql之锁的基本介绍

    数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则.对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外.MySQL数据 ...

  10. [jsp学习笔记] jsp基础知识 数据初始化、同步