一 分析

  要实现一个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. 下载pdf_不同操作系统,无法正常下载(兼容性问题)

    [功能点]:下载pdf文件 [问题描述]:window上传附件,linux无法下载 [根本原因]:window中路径分割符为"\",linux中路径分割符为"/" ...

  2. 20145320《Java程序设计》第五次实验报告

    20145320<Java程序设计>第五次实验报告 北京电子科技学院(BESTI)实验报告 课程:Java程序设计 班级:1453 指导教师:娄嘉鹏 实验日期:2016.05.06 18: ...

  3. vue.js入门(3)——组件通信

    5.2 组件通信 尽管子组件可以用this.$parent访问它的父组件及其父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据.另外,在子组件中修改父组件的 ...

  4. cocos2dx js 3.2 热更新

    COCOS IDE用手机调试更新是正常的,是预想的结果,但用COCOS IDE打包发布APK,安装到手机上,热更新下载图片.JSON UI什么的都能正常更新替换,但JS脚本没有替换,这是为毛.更新文件 ...

  5. SpringMvc处理JSON

    步骤如下: 1.加入jar包 2.编写目标方法,使其返回JSON对应的对象或集合 3.在方法上添加@ResponseBody注解 DispatcheServlet默认装配RequestMappingH ...

  6. 关于SQL Server无法查询中文的问题

    SQL Server 的版本是2016,随便试了一条 带有 where 子句的查询.如下: select * from Roles where RoleName like '%系统%' 呐尼,怎么一条 ...

  7. Linux之grep命令详解

    简介 grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它 ...

  8. degign new theme for Filezilla(Mac OS X)

    the theme directory is located at Filezilla.app/Contents/SharedSupport/resources/ the dirs (excludin ...

  9. 数据库的点数据根据行政区shp来进行行政区处理,python定时器实现

    # -*- coding: utf-8 -*- import struct import decimal import itertools import arcpy import math impor ...

  10. NS_ENUM vs. NS_OPTIONS

    NS_ENUM用于定义普通枚举值,NS_OPTIONS用于定义位移相关操作的枚举值: typedef NS_ENUM(NSUInteger, EOCConnectionState) { EOCConn ...