一 分析

  要实现一个shell,需包含3个步骤
  1)读入指令
  2)指令解析
  3)执行指令

1 从键盘读入指令

  从键盘读入指令的几个要点:

  1)调用getc函数等待并获取用户键盘输入。
  2)每一行命令的结束符为'\n',getsline函数就是通过这个结束符来判断用户是否完成指令的输入。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> int main()
{
char* cmdLine = (char*)malloc(sizeof(char)*);
char* prompt = "print your cmd >";
int i; while()
{ i = NextCmd(prompt,cmdLine);
if(i != )
{
return i;
}
else
{
printf("you print a cmd: %s \n",cmdLine);
}
}
free(cmdLine);
return ;
} int NextCmd(char* prompt,char* cmdLine)
{
int i;
printf("%s",prompt);
i = GetsLine(cmdLine);
if(i != )
{
return i;
}
else
{
return ;
}
} int GetsLine(char* result)
{
int word; while()
{
word = getc(stdin);
if(word != '\n')
{
*result = word;
result ++;
}
else
{
*result = '\0';
return ;
}
}
}

输出:

print your cmd >asd
you print a cmd: asd
print your cmd >a
you print a cmd: a
print your cmd >

  在上面的代码中我们从键盘上获得用户的指令输入,然后再直接打印出来。在GetsLine()函数执行getc(),等待用户输入并开始记录,直到用户输入回车后返回,返回前添加'\0'字符表示整条指令结束。在NextCmd()中输出提示符“print your cmd >”,执行GetsLine获得用户输入的整条指令并返回。最后在主函数中打印用户的输入。

2 指令解析

  指令解析的几个要点:

  1)支持的指令格式【指令】 【参数1】 【参数2】。。。【参数n】
  2)指令与参数间,参数与参数间均以空格隔开,一条完整的指令以'\0'结尾。
  3)因为使用函数execvp来执行指令(在后面会讲到),所以需要将解析的指令存入指针数组char* argList[]中,结尾处为NULL。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> int main()
{
char* argList[];
char* cmdLine = "abc -a -b -c";
int i;
for(i = ;i<;i++)
{
argList[i] = (char*)malloc(sizeof(char)*);
memset(argList[i],'\0',sizeof(char)*);
} CmdToArg(cmdLine,argList); for(i = ;i<;i++)
{
printf("get:%s\n",argList[i]);
} for(i = ;i<;i++)
{
free(argList[i]);
}
return ; } int CmdToArg(char* cmdLine,char* argList[])
{
char aChar;
char* pChar;
int i = ;
pChar = argList[];
while()
{
aChar = *cmdLine;
cmdLine++;
if(aChar == ' ')
{
*pChar = '\0';
i++;
pChar = argList[i];
}
else if(aChar == '\0')
{
*pChar = '\0';
i++;
argList[i] = ;
return ;
}
else
{
*pChar = aChar;
pChar++;
}
} }

  上述代码中,使用CmdToArg()函数,将cmdLine中的字符以空格符为间断符号解析,并存入一个argList中,且在argList的末尾处添加‘0’字符,表明一行指令的结束。

3 执行指令

  我们使用execvp()函数来启动另一个程序,这里有几点需要注意:

  1)新的程序会覆盖原程序,导致新程序结束时原本的shell也结束了,所以需要使用fork()函数来新建一个进程来打开新的程序。
  2)fork()函数的返回值可以判断当前进程是父进程还是子进程。
  3)在父进程中使用wait()函数来等待新进程结束

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> int main()
{
char* arglist[];
int i; arglist[] = "ls";
arglist[] = "-l";
arglist[] = ; i = fork();
if(i==)
{
printf("I'm the new process.ready for cmd ls\n");
execvp("ls",arglist);
}
else
{
wait(NULL);
printf("I'm the old process\n");
}
}

  在上面的代码中,我们使用fork()函数创建了一个新的进程,并在新进程中使用execvp()函数来启动新的程序,并在父进程中使用wait()函数来等待子进程结束。输出如下:

lqx@lqx-virtual-machine:~/bin/UnixPrograme/$ ./a.out
I'm the new process.ready for cmd ls
total
-rwxrwxr-x lqx lqx -- : a.out
-rw-rw-r-- lqx lqx -- : execute.c
-rw-rw-r-- lqx lqx -- : psh2.c
-rw-rw-r-- lqx lqx -- : smsh1.c
-rw-rw-r-- lqx lqx -- : smsh.h
-rw-rw-r-- lqx lqx -- : splitline.c
-rw-rw-r-- lqx lqx -- : test.c
-rwxrwxr-x lqx lqx -- : testline
-rw-rw-r-- lqx lqx -- : testline.c
I'm the old process

4 整合

  在前面,我们分别实现了:
  1)从用户终端获得用户的指令输入
  2)将一行指令解析为指定格式
  3)创建子进程来执行用户的指令

  现在就将以上3各部分整合到一起,实现一个基本的shell。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> int main()
{
char* cmdLine = (char*)malloc(sizeof(char)*);
char* prompt = "print your cmd >";
int i;
char* argList[];
for(i = ;i<;i++)
{
argList[i] = (char*)malloc(sizeof(char)*);
memset(argList[i],'\0',sizeof(char)*);
}
while()
{ i = NextCmd(prompt,cmdLine);
if(i != )
{
return i;
}
else
{
CmdToArg(cmdLine,argList);
i = fork();
if(i==)
{
execvp(argList[],argList);
exit();
}
else
{
wait(NULL);
}
}
}
free(cmdLine);
for(i = ;i<;i++)
{
free(argList[i]);
}
return ;
} int NextCmd(char* prompt,char* cmdLine)
{
int i;
printf("%s",prompt);
i = GetsLine(cmdLine);
if(i != )
{
return i;
}
else
{
return ;
}
} int GetsLine(char* result)
{
int word; while()
{
word = getc(stdin);
if(word != '\n')
{
*result = word;
result ++;
}
else
{
*result = '\0';
return ;
}
}
} int CmdToArg(char* cmdLine,char* argList[])
{
char aChar;
char* pChar;
int i = ;
pChar = argList[];
while()
{
aChar = *cmdLine;
cmdLine++;
if(aChar == ' ')
{
*pChar = '\0';
i++;
pChar = argList[i];
}
else if(aChar == '\0')
{
*pChar = '\0';
i++;
argList[i] = ;
return ;
}
else
{
*pChar = aChar;
pChar++;
}
} }

  测试一下:

print your cmd >ls -l
total
-rw-rw-r-- lqx lqx -- : \
-rwxrwxr-x lqx lqx -- : a.out
-rw-rw-r-- lqx lqx -- : execute.c
-rw-rw-r-- lqx lqx -- : psh2.c
-rw-rw-r-- lqx lqx -- : smsh1.c
-rw-rw-r-- lqx lqx -- : smsh.h
-rw-rw-r-- lqx lqx -- : splitline.c
-rw-rw-r-- lqx lqx -- : test.c
-rwxrwxr-x lqx lqx -- : testline
-rw-rw-r-- lqx lqx -- : testline.c

  以上,我们实现了一个基本的shell,虽然还有很多不足之处,但是对shell的基本原理和功能都有了一些了解。

UNIX-LINUX编程实践教程->第八章->实例代码注解->写一个简单的shell的更多相关文章

  1. Unix/Linux编程实践教程(三:代码、测试)

    测试logfilec.c的时候,有个sendto(sock,msg,strlen(msg),0,&addr,addrlen),编译时提示: logfilec.c:30: warning: pa ...

  2. 学习《Unix/Linux编程实践教程》(1):Unix 系统编程概述

    0.目录 1.概念 2.系统资源 3.学习方法 4.从用户的角度来理解 Unix 4.1 登录--运行程序--注销 4.2 目录操作 4.3 文件操作 5.从系统的角度来理解 Unix 5.1 网络桥 ...

  3. Unix/Linux编程实践教程(二:socket、多线程、进程间通信)

    同一接口不同的数据源: 协同进程: fdopen以文件描述符为参数: fopen和popen: 为了实现popen,必须在子进程中调用sh,因为只有shell本身即/bin/sh可以运行任意shell ...

  4. Unix/Linux编程实践教程(0:文件、终端、信号)

    本来只打算读这本书socket等相关内容,但书写得实在好,还是决定把其余的内容都读一下. 阅读联机帮助的一个示例: open系统调用: read系统调用: Unix的time: 上面的printf可以 ...

  5. Unix/Linux编程实践教程(一:进程、管道)

    execvp在程序中启动新程序: 用fork创建新进程: forkdemo2代码: 测试fork的时候参考<Linux权威指南>阅读笔记(3)  使用了patch: [root@local ...

  6. 学习《Unix/Linux编程实践教程》(2):实现 more

    0.目录 1.more 能做什么? 2.more 是如何实现的? 3.实现 more 3.1 more01.c 3.2 more02.c 3.3 more03.c 1.more 能做什么? more ...

  7. SmartSql使用教程(1)——初探,建立一个简单的CURD接口服务

    一.引言 最近SmartSql被正式引入到了NCC,借着这个契机写一个使用教程系列 二.SmartSql简介[摘自官方文档] 1. SmartSql是什么? SmartSql = MyBatis + ...

  8. linux设备驱动第三篇:如何写一个简单的字符设备驱动?

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  9. linux设备驱动第三篇:写一个简单的字符设备驱动

          在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...

随机推荐

  1. input/select/textarea标签的readonly效果实现

    首先说一下readonly属性的应用场景 表单中,不能编辑对应的文本,但是仍然可以聚焦焦点 在提交表单的时候,该输入项会作为form的一项提交(目的) 这里要说一下disabled和readonly的 ...

  2. 使用ocr的自动备份还原ocr

    1.查看ocr自动备份ocrconfig -showbackup 2.停止所有节点的集群件 3.还原ocr文件ocrconfig -restore <file-name> 4.重启crs, ...

  3. Anaconda安装与使用

    bash Anaconda.....sh命令安装成功 安装位置:/home/amelie/anaconda2 修改路径:vi ~/.bashrc vi编辑器在最后写上:export PATH=/hom ...

  4. jQuery对select操作小结

    //遍历option和添加.移除optionfunction changeShipMethod(shipping){ var len = $("select[@name=ISHIPTYPE] ...

  5. Latent Dirichlet Allocation 文本分类主题模型

    文本提取特征常用的模型有:1.Bag-of-words:最原始的特征集,一个单词/分词就是一个特征.往往一个数据集就会有上万个特征:有一些简单的指标可以帮助筛选掉一些对分类没帮助的词语,例如去停词,计 ...

  6. PHPwind高级伪静态规则及方法

    phpwind iis下伪静态规则[ISAPI_Rewrite]RewriteRule ^(.*)/(.*)-htm-(.*)-(.*)\.html$ $1/$2\.php\?$3=$4Rewrite ...

  7. java中如何获取昨天的当前日期

    在java里,获取昨天的当前日期,可以采用calendar来做,也可以采用date来做:如下: 1.采用calendar来做: Calendar cal=Calendar.getInstance(); ...

  8. [课程设计]Scrum 2.1 多鱼点餐系统开发进度(下单列表布局)

    [课程设计]Scrum 2.1 多鱼点餐系统开发进度(下单列表布局) 1.团队名称:重案组 2.团队目标:长期经营,积累客户充分准备,伺机而行 3.团队口号:矢志不渝,追求完美 4.团队选题:餐厅到店 ...

  9. WordCount示例深度学习MapReduce过程(1)

    我们都安装完Hadoop之后,按照一些案例先要跑一个WourdCount程序,来测试Hadoop安装是否成功.在终端中用命令创建一个文件夹,简单的向两个文件中各写入一段话,然后运行Hadoop,Wou ...

  10. python基础(内置函数+文件操作+lambda)

    一.内置函数 注:查看详细猛击这里 常用内置函数代码说明: # abs绝对值 # i = abs(-123) # print(i) #返回123,绝对值 # #all,循环参数,如果每个元素为真,那么 ...