学习理解shell的好办法--编写自己的shell 之二

shell脚本的最简单形式就是一串命令的罗列,shell充当解释器,一条条挨个执行,直到最后一个或遇到退出命令。但这只能做很简单的事情,只是省区了每次都要敲一边命令的时间,要想完成更负责的功能,还要加上这些东西:

1.控制

前面的条件满足了,然后干什么;不满足,干什么。

2.变量

c=a+b, 用一种形式代表另一种形式,就是变量。因为形式不同了,就能用一种不变的表示另一种变化的。比如“编程语言”就可以当一个变量,可以赋值为“C语言”,“Perl语言”,“Lisp”语言等。变量还可用缓冲思想理解,一个杯子,可暂存一杯水,一杯果汁,一杯酒,而这三个名称,又可归为“液体”这个变量名

3.环境

其实白马是属于马的,环境也是变量的一种。只不过它一般是由系统负责提供的,是为了免去软件每次运行都要设置一些变量的麻烦,比如设置语言什么的,有了环境就直接可以用这些变量了。常见的PATH,HOME. 命令env可看到你的系统的环境变量。

首先要做到工作是加入命令行解析,让用户可以在一行中输入命令和参数。解析器就输入的一行拆分成字符串数组,传给子进程的execvp。

讲一些信号的东西。信号是由单个词组成的消息,用于进程间通信,进程正在努力的跑着,你要和它说话,让它退出、暂停,就得用信号。kill -l可查看信号列表。

进程处理信号有三种情形,一是默认处理,通常是消亡,这个调用signal(SIGINT,SIG_DFL)恢复SIGINT的默认处理;二是忽略,通过这个调用signal(SIGINT,SIG_IGN);三是调用一个函数signal(signum,function);

2) SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程

3) SIGQUIT

和SIGINT类似, 但由QUIT字符(通常是Ctrl-\)来控制. 进程在因收到SIGQUIT退出时会产生core(吐核)文件。

20) SIGTSTP

停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号

在这一版shell中,处理SIGINT和SIGQUIT,在shell中忽略它俩,但在子shell中恢复他们的默认操作,因为这样可以在shell中要让子进程的结束,而不至于把shell自己杀死。

shell的main函数如下:

/* chicken_sh.c
 * 破壳,小鸡.
 * 增加了命令行处理,比egg_sh好用了
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include "smsh.h"

#define DFL_PROMPT "> "

void setup();
void fatal(char *s1, char *s2, int n)

int main()
{
  char *cmdline, *prompt, **arglist;
  int result;

  prompt = DFL_PROMPT;
  setup();

  while ((cmdline = next_cmd(prompt, stdin)) != NULL) {
    if ((arglist = splitline(cmdline)) != NULL) {
      result = execute(arglist);
      freelist(arglist);
    }
    free(cmdline);
  }
  return 0;
}

void setup()
/* 设置信号处理函数
 */
{
  signal(SIGINT, SIG_IGN);
  signal(SIGQUIT, SIG_IGN);
}

void fatal(char *s1, char *s2, int n)
/* 错误处理函数
 */
{
  fprintf(stderr, "Error: %s, %s\n", s1, s2);
  exit(n);
}

函数解释:

next_cmd  从输入读取下一个命令,用malloc分配内存以接受任意长度参数,碰到文件结束,返回NULL

splitline     解析参数. 将一个字符串分解为字符串数组,并返回这个数组

chicken_sh 比较复杂些,所以代码分成三个文件 :chicken_sh.c, splitline.c, execute.c  .(
源码下载)

这样编译:

gcc -o chichen_sh  chicken_sh.c splitline.c execute.c

其中的execute.c变化不大,只是增加了信号处理

splitline.c有点复杂,需要说明一下,代码如下:

/*splitline.c
 * 为chicken_sh读取并解析命令
 *char *next_cmd(char *prompt, FILE *fp)  取下一条指令
 *char **splitline(char *str);            解析字符串
 */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "chicken_sh.h"

char *next_cmd(char *prompt, FILE * fp)
{
    char *buf;
    int bufspace = 0;
    int pos=0;		/* 当前位置 */
    int c;		

    printf("%s", prompt);
    while ((c = getc(fp)) != EOF) {
	/*若需要空间*/
	if (pos + 1 >= bufspace) {
	    if (bufspace == 0)
		buf = emalloc(BUFSIZ);
	    else
		buf = erealloc(buf, bufspace + BUFSIZ);	/* 扩大分配的内存 */
	    bufspace += BUFSIZ;
	}

	/* 命令结束 */
	if (c == ‘\n‘)
	    break;

	/* 如果不结束,则添加进缓冲区 */
	buf[pos++] = c;
    }
    if (c == EOF && pos == 0)
	return NULL;
    buf[pos] = ‘\0‘;
    return buf;
}

#define is_delim(x) ((x) == ‘ ‘ || (x) == ‘\t‘)    /*参数分隔符是空格或tab*/

char *newstr(char *s, int l);
char **splitline(char *line)
{
    char **args;	/*要返回的参数数组*/
    int spots = 0;	/*参数指针的容量*/
    int bufspace = 0;	/*缓冲空间*/
    int argnum = 0;	/*参数计数*/
    char *cp = line;
    char *start;
    int len;

    if (line == NULL)		/* 什么输入也没有 */
	return NULL;

    args = emalloc(BUFSIZ);	/* 分配参数数组 */
    bufspace = BUFSIZ;
    spots = BUFSIZ / sizeof(char *);

    while (*cp != ‘\0‘) {
	while (is_delim(*cp))
	    cp++;
	if (*cp == "\0")
	    break;
	/* 确保参数数组的空间 */
	if (argnum + 1 >= spots) {
	    args = erealloc(args, bufspace + BUFSIZ);
	    bufspace += BUFSIZ;
	    spots += (BUFSIZ / sizeof(char *));
	}

	/* 标记开始的地方,查找以\0 结束的位置 */
	start = cp;
	len = 1;
	while (*++cp != ‘\0‘ && !(is_delim(*cp)))
	    len++;
	args[argnum++] = newstr(start, len);
    }
    args[argnum] = NULL;
    return args;
}

/*
 * 构造字符串,以‘\0‘ 结尾*/
char *newstr(char *s, int l)
{
    char *rv = emalloc(l + 1);

    rv[l] = ‘\0‘;
    strncpy(rv, s, l);
    return rv;
}

void freelist(char **list)
	/*参数用完后,释放空间*/
{
    char **cp = list;
    while (*cp)
	free(*cp++);
    free(list);
}

void *emalloc(size_t n)
{
    void *rv;
    if ((rv = malloc(n)) == NULL)
	fatal("out of memory", "", 1);
    return rv;
}

void *erealloc(void *p, size_t n)
{
    void *rv;
    if ((rv = realloc(p, n)) == NULL)
	fatal("realloc() failed", "", 1);
    return rv;
}

emalloc和erealloc为封装上错误处理的分配空间的函数;

next_cmd接受用户输入的命令和参数并保存在一个链表中;

splitline负责将接受的参数拆分成每个参数分开的一个指针数组,空间都是动态分配的, 因为用户输入命令时,各个参数是以空格或tab隔开的,newstr把他们拆分成字符独立的字符串,以‘\0‘结尾.

编译后的chicken_sh, 运行起普通命令来和正常shell没两样了,并且可以按ctrl+D退出。还可以做这几个改进:

1.一行多个命令,用分号隔开

2.后台进程,即命令最后加上 "&"

3.可以用exit命令退出,这样可以设置退出码,像exit 1, exit 0

下面增加if..then控制语句

shell中的if和C中的if一个不同之处是,C中if作用在它后面一条语句或花括号包住的语句块,而shell是用then开始,用"fi"表示if的结束,这样对于解释运行的程序流程设计来说是简单的。

还有,if是基于命令以0退出表示成功的假设,比如:

if cat hello.c
then
echo hello
fi

如果cat命令执行成功,返回0,那么then到fi的语句块就会执行。注意,命令返回0和直接在if后面敲个0是不同的,如果想直接说明真假,可以用true和false, if true就相当与if后面跟了个执行成功的命令,false反之。

太长了,下一篇再写...

学习理解shell的好办法--编写自己的shell 之二,布布扣,bubuko.com

时间: 2024-08-01 10:10:20

学习理解shell的好办法--编写自己的shell 之二的相关文章

学习理解shell的好办法--编写自己的shell 之一

本文参考自<Unix/Linux编程实践教程>, 这是一本讲解unix系统编程的书,注重实践,理解难度不大,推荐大家阅读,敲完本书后,对于理解unix系统如何运作会有更深的视角,回过头再学习别的 Linux相关的东西时,感受非常不一样,这是一本可以提高"内功"的书.自己加了些很菜的解释,以便其他小白理解,大牛直接飘过吧,错误之处希望指正. shell是一个管理进程和运行程序的程序,用来人和机器交互 常用的shell如sh,bash,zsh,csh,ksh等都有三个主要功能:

编写简单的Shell脚本

在一些复杂的Linux维护工作中,大量重复性的输入和操作不但费时费力,而且容易出错,而编写一个恰到好处的shell脚本程序,可以批量处理.自动化的完成一系列维护任务,大大减轻了管理员的负担 shell脚本基础 1. 编写第一个shell脚本 Linux中的shell脚本是一个特殊的应用程序,介于操作系统内核和用户之间,充当了一个命令解释器的角色,负责接收用户的操作指令并进行解释,将需要执行的操作传递给内核执行,并输出执行结果 常见的shell解释器 可通过/etc/shells文件了解当前系统所

bb_black_学习笔记——(3)点亮LED之shell命令

这次改变一下笔记的结构,直接上图.点亮LED永远是学习MCU,ARM,FPGA的入门例程,可以说是经典例程.这里笔者也从点亮LED开始开始ARM学习之旅. 接下来就开始点亮LED之旅,在开始之前需要向读者说明:本文作者也是一个ARM初学者,学习过程中也是在网上查找各种资料,最后总结于此.可以确定实验的过程中有好多地方笔者自己也是没有搞明白的,所以请读者原谅不能写的太透彻. 第一步:要点亮LED,首先需要了解GPIO的硬件位置,这样才能去操作相应的IO口,实现点灯目的 1.连接好bb-black之

Android中的context的学习理解

Android中Context的学习理解Context是一个抽象基类,通过它getResuources.getAssets and start 其他组件(Activity,Service,broadCast,getSystemService),可以这样理解:Context提供了一个运行环境for App, then app 可以访问资源,才能完成与其他组件,服务的交互,Context定义了一套基本的功能接口or一套规范 //todo

第十天:shell编程基础与编写Makefile

一:shell编程基础 shell定义:shell是一个作为用户与linux系统间接口的程序.它允许用户向操作系统输入需要执行的命令.shell有很多中,linux系统中shell为bash. shell编程可以看作是一堆命令的集合.和windows中的bat程序类似的脚本程序.为解释性语言. 第一个shell程序是判断两个数字的大小. 1 #!/bin/bash 2 3 num1=10 4 num2=9 5 6 if test $num1 -gt $num2 7 then 8 echo $nu

Hadoop基础学习(一)分析、编写并运行WordCount词频统计程序

前面已经在我的Ubuntu单机上面搭建好了伪分布模式的HBase环境,其中包括了Hadoop的运行环境. 详见我的这篇博文:http://blog.csdn.net/jiyiqinlovexx/article/details/29208703 我的目的主要是学习HBase,下一步打算学习的是将HBase作为Hadoop作业的输入和输出. 但是好像以前在南大上学时学习的Hadoop都忘记得差不多了,所以找到以前上课做的几个实验:wordCount,PageRank以及InversedIndex.

学习理解 makefile

学习理解 makefile 模拟个应用的例子: 有个工程包括头文件 001.h.002.h.003.h.004.h.005.h.006.h.007.h 共7个:程序文件 001.c.002.c.003.c.004.c.005.c.006.c.007.c.008.c.009.c.010.c 共10个文件.看着头大吧,先不关心具体内容. 现在来编译该工程.如下: # cd example/ # gcc 001.c 002.c 003.c 004.c 005.c 006.c 007.c 008.c 0

理解TCP/IP网络栈&amp;编写网络应用(下)

1.摘要 这是<翻译:理解TCP/IP网络栈&编写网络应用>的下篇,文章中会通过讲解TCP的代码实现帮助大家理解发送.接收数据的流程,也描述了一些网卡.驱动等网络栈底层的原理. 原文地址:原文地址 原作者:Hyeongyeop Kim 2.数据结构 以下是一些关键数据结构.我们了解一下这些数据结构再开始查看代码. 2.1.sk_buff_structure 首先,sk_buff结构或skb结构代表一个数据包.图6展现了sk_buff中的一些结构.随着功能变得更强大,它们也变得更复杂了.

小鸟初学Shell编程(二)编写简单的Shell脚本

Shell脚本 编写Python.PHP脚本通常需要掌握语言的函数,那么Shell脚本则不需要,只需要掌握Linux命令就可以编写Shell脚本,因为Shell脚本就是由多个Linux命令组成,通过将多个Linux命令组合保存成一个脚本文件,可直接给其他人使用. 组合命令 进入一个目录,查看目录的文件,这个过程分别需要执行两条命令,分别是cd 和ls. 分开执行两个命令的形式如下: [[email protected] usr]# cd /usr/ [[email protected] usr]