UNIX-LINUX编程实践教程->第八章->实例代码注解->写一个简单的shell
一 分析
要实现一个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的更多相关文章
- Unix/Linux编程实践教程(三:代码、测试)
测试logfilec.c的时候,有个sendto(sock,msg,strlen(msg),0,&addr,addrlen),编译时提示: logfilec.c:30: warning: pa ...
- 学习《Unix/Linux编程实践教程》(1):Unix 系统编程概述
0.目录 1.概念 2.系统资源 3.学习方法 4.从用户的角度来理解 Unix 4.1 登录--运行程序--注销 4.2 目录操作 4.3 文件操作 5.从系统的角度来理解 Unix 5.1 网络桥 ...
- Unix/Linux编程实践教程(二:socket、多线程、进程间通信)
同一接口不同的数据源: 协同进程: fdopen以文件描述符为参数: fopen和popen: 为了实现popen,必须在子进程中调用sh,因为只有shell本身即/bin/sh可以运行任意shell ...
- Unix/Linux编程实践教程(0:文件、终端、信号)
本来只打算读这本书socket等相关内容,但书写得实在好,还是决定把其余的内容都读一下. 阅读联机帮助的一个示例: open系统调用: read系统调用: Unix的time: 上面的printf可以 ...
- Unix/Linux编程实践教程(一:进程、管道)
execvp在程序中启动新程序: 用fork创建新进程: forkdemo2代码: 测试fork的时候参考<Linux权威指南>阅读笔记(3) 使用了patch: [root@local ...
- 学习《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 ...
- SmartSql使用教程(1)——初探,建立一个简单的CURD接口服务
一.引言 最近SmartSql被正式引入到了NCC,借着这个契机写一个使用教程系列 二.SmartSql简介[摘自官方文档] 1. SmartSql是什么? SmartSql = MyBatis + ...
- linux设备驱动第三篇:如何写一个简单的字符设备驱动?
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
- linux设备驱动第三篇:写一个简单的字符设备驱动
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...
随机推荐
- 下载pdf_不同操作系统,无法正常下载(兼容性问题)
[功能点]:下载pdf文件 [问题描述]:window上传附件,linux无法下载 [根本原因]:window中路径分割符为"\",linux中路径分割符为"/" ...
- 20145320《Java程序设计》第五次实验报告
20145320<Java程序设计>第五次实验报告 北京电子科技学院(BESTI)实验报告 课程:Java程序设计 班级:1453 指导教师:娄嘉鹏 实验日期:2016.05.06 18: ...
- vue.js入门(3)——组件通信
5.2 组件通信 尽管子组件可以用this.$parent访问它的父组件及其父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据.另外,在子组件中修改父组件的 ...
- cocos2dx js 3.2 热更新
COCOS IDE用手机调试更新是正常的,是预想的结果,但用COCOS IDE打包发布APK,安装到手机上,热更新下载图片.JSON UI什么的都能正常更新替换,但JS脚本没有替换,这是为毛.更新文件 ...
- SpringMvc处理JSON
步骤如下: 1.加入jar包 2.编写目标方法,使其返回JSON对应的对象或集合 3.在方法上添加@ResponseBody注解 DispatcheServlet默认装配RequestMappingH ...
- 关于SQL Server无法查询中文的问题
SQL Server 的版本是2016,随便试了一条 带有 where 子句的查询.如下: select * from Roles where RoleName like '%系统%' 呐尼,怎么一条 ...
- Linux之grep命令详解
简介 grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它 ...
- degign new theme for Filezilla(Mac OS X)
the theme directory is located at Filezilla.app/Contents/SharedSupport/resources/ the dirs (excludin ...
- 数据库的点数据根据行政区shp来进行行政区处理,python定时器实现
# -*- coding: utf-8 -*- import struct import decimal import itertools import arcpy import math impor ...
- NS_ENUM vs. NS_OPTIONS
NS_ENUM用于定义普通枚举值,NS_OPTIONS用于定义位移相关操作的枚举值: typedef NS_ENUM(NSUInteger, EOCConnectionState) { EOCConn ...