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

加入if...then后,then作用的部分,在条件失败时,里面的命令不执行,不传给子进程,条件成功才传给子进程。因此,if,then,fi是控制命令,要和普通命令区分开。在splitline之后,在fork之前,应加一层判断,看解析后的命令是不是控制命令。这里的判断不是用if直接判断,否则就无穷的递归了....而是用原始的字符比较,如果命令字符是"if","then","fi"那就是属于控制.

增加的这层叫process,它负责管理流程。流程图是这样的(略去了信号处理):

                     取得命令                     
      ---------->解析命令---------->exit
     |                |                   
     | ***********************
     | *    y        |                   *
     |<*--------是否为控制?       *
     | *              |                  *
     | *              |n                *
     | *              |                  *
     | *     -----fork------          *
     | *     |                |          *
     | *    wait         execvp     *
     | *     |                |          *
     | *     |                |          * 星号方框内是process函数
      -*----<-----------exit        *
       *                                  *
       ***********************
                          
process将脚本分成四种区域:if之前和fi之后的中立区neutral;   if和then之间的want-then;   then到else之间的then-block;  else到fi的else-block.
中立区代码挨个执行;
want-then中,每执行一条就记录其退出状态,一般是一条需要if判断执行正确与否的指令;
else-block有时候可以没有。

shell记录当前区域,还记录want-then区的执行结果

基于chicken_sh.c修改后的main函数只改一个地方,把调用,execute的地方换成process,这样就用process封装了命令执行部分。增加了两个文件process.c和controlflow.c ( 源码下载), 后者中有这三个处理区域的函数:
is_control_command: 判断读入的这个命令是普通命令还是"if","then"这种属于shell的关键字
do_control_command:处理关键字
ok_to_execute:根据状态和条件命令的结果返回一个真假值,说明能否执行当前命令
controlflow.c的代码中,不带else的控制共有三个状态,NEUTRAL, WANT_THEN, THEN_BLOCK,用if_state表示, if_result有SUCCESS,FAIL两种结果,表示if后的命令是否成功执行。加上else后多了WANT_ELSE, ELSE_BLOCK
do_control_command函数如下:


#include<stdio.h>
#include "chicken_sh.h"

enum states { NEUTRAL, WANT_THEN, THEN_BLOCK, WANT_ELSE, ELSE_BLOCK };
enum results { SUCCESS, FAIL };

static int if_state = NEUTRAL;
static int if_result = SUCCESS;
static int last_stat = 0;

int syn_err(char *);

int ok_to_execute()
/*决定shell是否执行一条命令
 * 是则返回1,否则返回0
 */
{
	int rv = 1;

	if (if_state == WANT_THEN || if_state == WANT_ELSE) {
		syn_err("then or else expected");
		rv = 0;
	} else if (if_state == THEN_BLOCK && if_result == SUCCESS)
		rv = 1;
	else if (if_state == THEN_BLOCK && if_result == FAIL)
		rv = 0;
	else if (if_state == ELSE_BLOCK && if_result == FAIL)
		rv = 1;
	else if (if_state == ELSE_BLOCK && if_result == SUCCESS)
		rv = 0;
	return rv;
}

int is_control_command(char *s)
{
	return (strcmp(s, "if") == 0 || strcmp(s, "then") == 0 ||
		strcmp(s, "else") == 0 || strcmp(s, "fi") == 0);
}

int do_control_command(char **args)
{
	char *cmd = args[0];
	int rv = -1;

	if (strcmp(cmd, "if") == 0) {
		if (if_state != NEUTRAL)
			rv = syn_err("if unexpected");
		else {
			last_stat = process(args + 1);
			if (last_stat == 0) {
				if_result = SUCCESS;
				if_state = WANT_THEN;
			} else {
				if_result = FAIL;
				if_state = WANT_ELSE;
			}
			rv = 0;
		}
	} else if (strcmp(cmd, "then") == 0) {
		if (if_state != WANT_THEN)
			rv = syn_err("then unexpected");
		else {
			if_state = THEN_BLOCK;
			rv = 0;
		}
	} else if (strcmp(cmd, "else") == 0) {
		if (if_state != WANT_ELSE)
			rv = syn_err("else unexpected");
		else {
			if_state = ELSE_BLOCK;
			rv = 0;
		}
	} else if (strcmp(cmd, "fi") == 0) {
		if (if_state != THEN_BLOCK || if_state != ELSE_BLOCK)
			rv = syn_err("fi unexpected");
		else {
			if_state = NEUTRAL;
			rv = 0;
		}
	} else
		fatal("internal error processing:", cmd, 2);
	return rv;
}

int syn_err(char *msg)
{
	if_state = NEUTRAL;
	fprintf(stderr, "syntax error: %s\n", msg);
	return -1;
}


增加变量的这几种处理:赋值,引用,列出,导出(export),都为shell内置命令。

变量保存在一个数据中,global这个布尔变量表示已导出。

修改process函数,在execute执行前,增加一个判断,看是否为内置命令: if(!builtin_command(args,&rv)).

该函数在builtin.c文件中(source code)


#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include"chicken_sh.h"
#define MAXVARS 200


int assign(char *);
int okname(char *);



struct var{
    char *str;
    int global;
}

static struct var tab[MAXVARS]


 int builtin_command(char **args, int *resultp)
/* run a builtin command
   return: 1 if args[0] is builtin, 0 if not
*/
{
  int rv = 0;

  if (strcmp(args[0],"set") == 0) { /* 'set' command? */
    VList();
    *resultp = 0;
    rv = 1;
  }else if (strchr(args[0], '=') != NULL) { /* assignment cmd */
    *resultp = assign(args[0]);
    if (*resultp != -1)
      rv = 1;
  }else if (strcmp(args[0], "export") == 0) {
     if (args[1] != NULL && okname(args[1]))
      *resultp = VLexport(args[1]);
    else
      *resultp = 1;
    rv = 1;
  }
  return rv;
}

int assign(char *str)
/* 
 * 赋值:name=value
 */
{
  char *cp;
  int rv;

  cp = strchr(str, '=');
  *cp = '\0';
  rv = (okname(str)?VLstore(str, cp + 1):-1);
  *cp = '=';
  return rv;
}

int okname(char *str)
/* 
   determines if a string is a legal variable name */
{
  char *cp;

  for (cp = str; *cp; cp++ ) {
    if ((isdigit(*cp) && cp == str) || !(isalnum(*cp) || *cp == '_'))
      return 0;
  }
  return (cp != str);		/* no empty strings, either */
}

用到的VLstore增加更新名值对,VLookup取得var的值,VList输出列表在varlib.c中,此文件将环境一起实现

另外big_chicken_sh.c中,setup要增加VLenviron2table,将环境复制到变量表,即继承环境的变量。

你可能感兴趣的:(c,linux,shell,系统编程)