加入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,将环境复制到变量表,即继承环境的变量。