如何写一个简单的shell

如何写一个简单的shell

看完《UNIX环境高级编程》后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下。

源代码放在了Github: https://github.com/Broglie/Oh-Shell

简单的分析

我们的shell不像bash那样复杂全面,只是实现其中的一小部分功能:命令历史,命令补全,支持IO重定向和管道。一共分成几个部分:主

函数文件,输出出错信息,解析命令等。

我们打开bash对照着做,首先bash有命令提示符,我们要做的和bash的命令提示符一样。然后我们读取用户输入的命令并使用readline库

将命令添加到历史命令列表。然后我们将命令传递给解析命令函数,解析命令并执行。

输出命令提示符

打开bash后我们看到初始的命令提示符如下图所示:

如何写一个简单的shell

以root登录后用cd命令切换到别的工作目录后如下图所示:

如何写一个简单的shell

我们发现bash的命令提示符格式为[用户名]@[主机名]:[当前工作目录][$或#]。可以看出用户的home目录以~表示,而结尾部分的$或#是

指如果当前用户是普通用户,则命令提示符是$;如果当前用户是root用户,则命令提示符为#。

那么该如何实现呢?我们用getpwuid函数获取用户的信息,包括用户名和用户ID;用gethostname函数获取主机名;用getcwd函数获

取当前用户的当前工作目录。有了用户ID我们就能判断当前用户是不是root,因为root用户的用户ID为0。有了当前工作目录我们就能判断

工作目录是不是在用户的home目录下,如果在home目录下我们就将home目录的部分替换成~。这样打印提示符的任务就完成了,此部分代码

的实现在main.c文件中的getPrompt函数中。

命令历史和命令补全

Linux默认保存最后输入的500个命令历史。我们可以用GNU的readline库实现命令历史和命令补全。安装就按其中的说明的步骤进

行就行。readline的库有很多功能,但我们只需要其中的readline函数和add_history函数就行了。readline函数以一个字符串为参

数作为提示符,返回用户输入的一行命令;add_history函数以一个字符串作为参数,并将此字符串添加到历史命令列表里。

打印出错消息

在err.h头文件中声明了3个错误处理函数:err_ret函数打印出错消息并返回,err_quiterr_sys函数打印出错消息并退出程序。其

后两个函数在发生致命错误时调用,而err_ret函数在发生非致命错误时调用。其实现在err.c文件中。其实现是参照《UNIX环境高级编程》

中的出错处理函数。

实现IO重定向和管道

处理诸如cat < in.data | grep str | sort > out.data这样的命令需要实现IO重定向和管道。默认情况下在shell中运行的程序其标准输

入(描述符为0)和标准输出(描述符为1)都关联到终端,IO重定向是指将程序的标准输入和标准输出重定向到文件或其他设备。shell从描

述符0读,从描述符1写。如果我们想将标准输入重定向就得关闭描述符0,再将其他文件或设备关联到描述符0。对于标准输出也一样。

管道是将进程的标准输入和/或标准输出通过一个数据通道与另一个进程关联。以本节开头的那个例子来说,grep的标准输入来自cat

序的输出,标准输出则作为sort程序的标准输入。我们可以用pipe函数创建一个管道,其参数是一个int型数组(假设为fd[2]),数组有

两个元素。经由数组返回两个文件描述符,fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。如果想重定向标准输入,则将

标准输入与fd[0]相关联,如果想重定向标准输出,则将标准输出与fd[1]相关联。

命令解析

我认为命令解析是这个程序中最难的部分了,可能是我没学过编译原理的原因。所以我死扣了好长时间还是不会怎样解析shll命令(现在也没

搞通)。我偶然发现在xv6课程主页上有一个homework是关于这个的,它给出了解析命令的框架,将关键的部分空出来

让学生补全,所以我就照搬出来用了。所以实现的功能也有限,还有命令列表和后台命令不会解析。等以后我看了编译原理后再把这个坑填了。

总结

这个shell虽然简单,但也让我学到了很多知识,查了很多资料。看完《UNIX环境高级编程》后收获了很多,最高兴的莫过于将学到的知识用在

实际的程序中。就写到这里吧。

Reference:

《UNIX环境高级编程 第三版》

xv6: https://pdos.csail.mit.edu/6.828/2014/xv6.html

上一篇:CF722C. Destroying Array[并查集 离线]


下一篇:rpm命令用法小结