# 前言
最近操作系统结课了,不过还有一个课程设计需要完成。可供选择的题目并不少:Shell编程、系统调用、文件系统等等。当时觉得Shell编程看起来很有挑战性,实现后应该会很炫酷,而且课程设计指导上对Shell编程介绍的篇幅也是最大的。老师或许会给这个课题最高的分数? 于是没有太多犹豫就确定了下来。
本以为自己写一个Shell不简单,等到多看了几篇博客理解了其中原理后,才发现要实现一个简单的Shell其实并不困难。它就是对之前操作系统实验的综合运用。
关于如何用c来写一个Shell,网上已经有很多很棒的博客啦。比如山城过雨这位大神写的文章——《myShell:Linux Shell 的简单实现》,写的非常详细,只需要认真阅读一遍就能对整体流程了解地差不多了。我写这篇博客,不会去过多描述相同的内容,而是会记录一些自己做课程设计中的一些心得。
# 功能与展示
要做什么,标题已经说得很清楚了。那么再展示一下实现的功能吧,是不是你所想要的,一眼便能看明白。
# 功能列表
- 编写一个C语言程序作为Linux内核的shell命令行解释程序,所执行的结果需和系统命令行方式保持一致。
- 增加后台运行功能。即用户可以使用”&”作为一个命令结束,以启动下一个命令。
- 增加I/O重定向功能。即用户可以使用”<”和”>”符号改变程序/文件的输入和输出。
- 增加管道功能。即支持以“|”进行进程间通信操作。
- 增加退出功能。输入“exit”命令或者Ctrl +D退出。
- 增加文件名替换功能。(课设要求上是这么写的,我理解为mv命令的使用)
- 增加命令补全功能。即按下tab键可以补全命令。
- 增加查阅历史记录的功能。可以查看历史命令。
- 支持目录检索功能。即文件不存在,继续打印提示符。
- 支持一定的错误输入处理。例如:多于空格的出现,输入命令不存在,空输入等等。
# 功能展示
简单放几张展示图,分别为帮助功能、查看历史命令功能和I/O重定向功能。Shell提示符模仿的是命令提示符的样式,并在头部加入了红色的Myshell字样用于区分。
![](img/1.png)
# 依赖库安装
在Shell的编写过程中,需要用到readline库,借此实现tab键代码补全、查看历史命令等功能。当然,readline库的功能远不止于此,如果想深入了解并运用,可以阅读官方文档。
我的虚拟机上安装的系统是Ubuntu 16.04.6 LTS。
redhat系列下这个软件包叫readline-devel,ubuntu下叫readline-dev,细分为libreadline5-dev和libreadline6-dev。但是apt安装时发现,libreadline5-dev已经不存在了,不过libreadline6-dev还是在的,于是可以通过以下命令进行安装:
```
sudo apt-get install libreadline6-dev
```
此外,readline库还依赖于一个底层输入输出的库——ncurses,安装命令行如下:
```
sudo apt-get install libncurses5-dev
```
到此为止,依赖库的安装配置已经完成。
# 具体实现
## Shell工作流程
### Shell的工作流程如下图所示:
![](img/2.png)
其实实现原理很简单,使用while循环持续接收用户命令,根据读入的字符串判断命令类型并进行相应的处理。
接下来,我会以流程图与代码结合的说明工作流程。
### 外部命令工作流程
外部命令工作流程如下图所示:
![](img/3.png)
以“mv t1 t2”命令为例,首先程序读入用户输入的字符串,存入字符串数组command中。调用analysis_command()函数进行分析:
```c
int i = 1;
char *p;
//分割依据,这里的分割依据为空格
char delims[] = " ";
argc = 1;
//将command字符串中第一个空格以前的字符串存入argv[0],这里是"mv"
strcpy(argv[0],strtok(command,delims));
//继续对剩余字符串进行切分,并将切分结果存入argv数组中
while(p = strtok(NULL,delims)){
strcpy(argv[i++],p);
argc++;
}
//判断输入的指令是否为内置命令
if(!(strcmp(argv[0],"exit"))||!(strcmp(argv[0],"help"))|| !(strcmp(argv[0],"cd"))||!(strcmp(argv[0],"history"))){
BUILTIN_COMMAND = 1;
}
//判断输入的指令是否含有管道功能的符号
int pipe_location;
for(int j = 0;j < argc;j++){
if(strcmp(argv[j],"|") == 0){
PIPE_COMMAND = 1;
pipe_location = j;
break;
}
}
...
//中间省略了其他指令的判定及处理
if(PIPE_COMMAND){
...
}
...
else{
//argvtmp1存储分割后的指令
argvtmp1 = malloc(sizeof(char *)*argc+1);
int i;
for(i = 0;i < argc + 1;i++){
argvtmp1[i] = malloc(sizeof(char)*100);
if(i < argc)
strcpy(argvtmp1[i],argv[i]);
}
//execvp函数中,参数列表的最后一项必须是 NULL
argvtmp1[argc] = NULL;
}
```
得到以空格分割后的指令数组argvtmp1后,调用do_command()函数执行指令:
```c
if(PIPE_COMMAND){
...
}
...
else{
//调用fork()函数创建子进程,在子进程中执行指令
pid_t pid = fork();
if(pid == -1){
printf("fork failed !\n");
}
else if(pid == 0){
//调用execvp()函数执行程序,第一个参数是要运行的程序名,第二个参数是命令行参数列表
//若失败则返回值为-1,输出"command not found"的提示
if(execvp(argvtmp1[0],argvtmp1) < 0){
printf("%s:command not found\n",argvtmp1[0]);
}
}
else{
int pidReturn = wait(NULL);
}
}
//释放malloc( )分配的内存
free(argvtmp1);
```
外部命令执行结束,回到主程序并打印提示符。
## 内置命令工作流程
内置命令的工作流程如下图所示:
![](img/4.png)
内置命令的工作流程与外部命令的工作流程大同小异,只是多了一步执行对应函数的操作。这个对应函数可以由自己编写,比如help命令:
```c
void builtin_command(){
...
//判断输入指令是否为"help"
if(strcmp(argv[0],"help") == 0){
help();
}
...
}
void help(){
//打印用户名和内置命令列表
struct passwd* pwp;
pwp = getpwuid(getuid());
printf("Hi, %s !\n",pwp->pw_name);
printf("Here are the built-in commands:\n\n");
printf("1. cd\n");
printf("2. history\n");
printf("3. help\n");
printf("4. exit\n");
}
```
也可以调用已有的函数,比如要实现cd指令,就需要用到chdir()函数。
## 管道功能与I/O重定向的实现
管道功能与I/O重定向的实现,与上面内置命令与外部命令的工作流程没有多大区别,前人之述备矣。读过之后我在Shell中加入了简单的输入重定向,没有遇到太大的问题,就不再赘述了。
## alias功能的一些思考
alias不是外部命令,只能调用函数或者自己实现。在网上找了好久有关alias的工作原理,不知是描述不准确还是别的原因,我并没看到描述alias工作原理的相关文章,也没找到某个库是附带alias功能函数的。不过看的多了,也就有了一点自己的想法,先挖个坑记录下来,等以后有时间了再埋。
目前的思路是,在用户主目录下的.bashrc文件中记录存放alias命令的文件名(比如.aliases)。然后于Shell内置命令中添加alias和unalias的指令判定,添加的别名都存入文件.aliases中。(感觉alias的实现原理大致就是如此,但在没有看到某篇文章明确说之前,也不敢下结论)
## Shell的编译与运行
readline是动态链接库,gcc时需要加上-lreadline。ncurses作为readline依赖的底层输入输出库,需要放在readline的后面。
```
gcc MyShell.c -o MyShell -lreadline -lncurses
```
输入./MyShell即可进入Shell。
![](img/5.png)
原文链接:https://blog.csdn.net/qq_43326014/article/details/107215174
没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
编写一个C语言程序作为Linux内核的shell命令行解释程序,所执行的结果需和系统命令行方式保持一致。 增加后台运行功能。即用户可以使用”&”作为一个命令结束,以启动下一个命令。 增加I/O重定向功能。即用户可以使用”<”和”>”符号改变程序/文件的输入和输出。 增加管道功能。即支持以“|”进行进程间通信操作。 增加退出功能。输入“exit”命令或者Ctrl +D退出。 增加文件名替换功能。(课设要求上是这么写的,我理解为mv命令的使用) 增加命令补全功能。即按下tab键可以补全命令。 增加查阅历史记录的功能。可以查看历史命令。 支持目录检索功能。即文件不存在,继续打印提示符。 支持一定的错误输入处理。例如:多于空格的出现,输入命令不存在,空输入等等。
资源推荐
资源详情
资源评论
收起资源包目录
100012094-使用C语言编写一个Linux的外壳Shell(操作系统课程).zip (10个子文件)
ctoshell
Code
MyShell 19KB
MyShell.c 9KB
README.md 1KB
LICENSE 1KB
img
3.png 172KB
1.png 360KB
5.png 106KB
4.png 132KB
2.png 102KB
README.md 8KB
共 10 条
- 1
资源评论
神仙别闹
- 粉丝: 2671
- 资源: 7640
下载权益
C知道特权
VIP文章
课程特权
开通VIP
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功