1.在上一次的基础上,我们知道调用execute运行一个命令,从wait函数中得到子进程的退出状态.这里在shell中加入if语句,需要将if之后命令的结果存放在一些变量中,然后要知道后面读入的命令是在then块中,还是else块中.最后还得确保在if之后读入then.
在原来的模型上添加一个process函数来实现。
修改前后流程图如图所示。
process通过寻找关键字比如if then,fi等来管理脚本流程,在适当的时候调用fork和exec,其必须记录条件命令的结果以便处理then和else块。
2. 下面再加入变量的功能,要在shell中加入变量,必须有个地方能存放这些变量的名称和值,而且这个变量存储系统必须能够分辨局部和全局变量。下面是这个存储系统的抽象模型:
接口:VLstore(char *var,char *val) 增加/更新var = val
VLookup(char *var) 取得var的值
VList输出列表到stdout
实现:这里可以用链表,hash表,树或者是几乎任何数据结构来实现它。这里简单起见用一个结构数组
stuct var{
char *str; /*name*/
int global; /*a boolean*/
};
static struct var tab[MAXVARS];
3.此时已经有存放变量的地方了,但还要增加给变量赋值,列出所有变量和获取变量值的命令。此时对流程图做一些修改以便在调用fork和exec之前必须先看看命令是否是shell内置的命令,如下图:
在process中加入builtin_command函数,将检查和执行内置命令合并在一起。其代码如下:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "smsh.h"
#include "varlib.h"
int assign(char *);
int okname(char *);
int builtin_command(char **args, int *resultp)
{
int rv = 0;
if ( strcmp(args[0],"set") == 0 ){ /* 'set' command? */
VLlist();
*resultp = 0;
rv = 1;
}
else if ( strchr(args[0], '=') != NULL ){ /* assignment cmd */
*resultp = assign(args[0]);
if ( *resultp != -1 ) /* x-y=123 not ok */
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)
{
char *cp;
int rv ;
cp = strchr(str,'=');
*cp = '\0';
rv = ( okname(str) ? VLstore(str,cp+1) : -1 );
*cp = '=';
return rv;
}
int okname(char *str)
/*
* purpose: determines if a string is a legal variable name
* returns: 0 for no, 1 for yes
*/
{
char *cp;
for(cp = str; *cp; cp++ ){
if ( (isdigit(*cp) && cp==str) || !(isalnum(*cp) || *cp=='_' ))
return 0;
}
return ( cp != str ); /* no empty strings, either */
}
附上完整代码:
/** smsh3.c
**/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include "smsh.h"
#define DFL_PROMPT "> "
int main()
{
char *cmdline, *prompt, **arglist;
int result, process(char **);
void setup();
prompt = DFL_PROMPT ;
setup();
while ( (cmdline = next_cmd(prompt, stdin)) != NULL ){
if ( (arglist = splitline(cmdline)) != NULL ){
result = process(arglist);
freelist(arglist);
}
free(cmdline);
}
return 0;
}
void setup()
/*
* purpose: initialize shell
* returns: nothing. calls fatal() if trouble
*/
{
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);
}
/* process2.c
*/
#include <stdio.h>
#include "smsh.h"
int is_control_command(char *);
int do_control_command(char **);
int ok_to_execute();
int builtin_command(char **, int *);
int process(char **args)
/*
* purpose: process user command
* returns: result of processing command
* details: if a built-in then call appropriate function, if not execute()
* errors: arise from subroutines, handled there
*/
{
int rv = 0;
if ( args[0] == NULL )
rv = 0;
else if ( is_control_command(args[0]) )
rv = do_control_command(args);
else if ( ok_to_execute() )
if ( !builtin_command(args,&rv) )
rv = execute(args);
return rv;
}
/* execute.c - code used by small shell to execute commands */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int execute(char *argv[])
/*
* purpose: run a program passing it arguments
* returns: status returned via wait, or -1 on error
* errors: -1 on fork() or wait() errors
*/
{
int pid ;
int child_info = -1;
if ( argv[0] == NULL ) /* nothing succeeds */
return 0;
if ( (pid = fork()) == -1 )
perror("fork");
else if ( pid == 0 ){
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
execvp(argv[0], argv);
perror("cannot execute command");
exit(1);
}
else {
if ( wait(&child_info) == -1 )
perror("wait");
}
return child_info;
}
/* controlflow.c
*/
#include <stdio.h>
#include "smsh.h"
enum states { NEUTRAL, WANT_THEN, THEN_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()
/*
* purpose: determine the shell should execute a command
* returns: 1 for yes, 0 for no
* details: if in THEN_BLOCK and if_result was SUCCESS then yes
* if in THEN_BLOCK and if_result was FAIL then no
* if in WANT_THEN then syntax error (sh is different)
*/
{
int rv = 1; /* default is positive */
if ( if_state == WANT_THEN ){
syn_err("then 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;
return rv;
}
int is_control_command(char *s)
/*
* purpose: boolean to report if the command is a shell control command
* returns: 0 or 1
*/
{
return (strcmp(s,"if")==0 || strcmp(s,"then")==0 || strcmp(s,"fi")==0);
}
int do_control_command(char **args)
/*
* purpose: Process "if", "then", "fi" - change state or detect error
* returns: 0 if ok, -1 for syntax error
* notes: I would have put returns all over the place, Barry says "no"
*/
{
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_result = (last_stat == 0 ? SUCCESS : FAIL );
if_state = WANT_THEN;
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,"fi")==0 ){
if ( if_state != THEN_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)
/* purpose: handles syntax errors in control structures
* details: resets state to NEUTRAL
* returns: -1 in interactive mode. Should call fatal in scripts
*/
{
if_state = NEUTRAL;
fprintf(stderr,"syntax error: %s\n", msg);
return -1;
}
/* varlib.c
*/
#include <stdio.h>
#include <stdlib.h>
#include "varlib.h"
#include <string.h>
#define MAXVARS 200 /* a linked list would be nicer */
struct var {
char *str; /* name=val string */
int global; /* a boolean */
};
static struct var tab[MAXVARS]; /* the table */
static char *new_string( char *, char *); /* private methods */
static struct var *find_item(char *, int);
int VLstore( char *name, char *val )
/*
* traverse list, if found, replace it, else add at end
* since there is no delete, a blank one is a free one
* return 1 if trouble, 0 if ok (like a command)
*/
{
struct var *itemp;
char *s;
int rv = 1;
/* find spot to put it and make new string */
if ((itemp=find_item(name,1))!=NULL && (s=new_string(name,val))!=NULL)
{
if ( itemp->str ) /* has a val? */
free(itemp->str); /* y: remove it */
itemp->str = s;
rv = 0; /* ok! */
}
return rv;
}
char * new_string( char *name, char *val )
/*
* returns new string of form name=value or NULL on error
*/
{
char *retval;
retval = malloc( strlen(name) + strlen(val) + 2 );
if ( retval != NULL )
sprintf(retval, "%s=%s", name, val );
return retval;
}
char * VLlookup( char *name )
/*
* returns value of var or empty string if not there
*/
{
struct var *itemp;
if ( (itemp = find_item(name,0)) != NULL )
return itemp->str + 1 + strlen(name);
return "";
}
int VLexport( char *name )
/*
* marks a var for export, adds it if not there
* returns 1 for no, 0 for ok
*/
{
struct var *itemp;
int rv = 1;
if ( (itemp = find_item(name,0)) != NULL ){
itemp->global = 1;
rv = 0;
}
else if ( VLstore(name, "") == 1 )
rv = VLexport(name);
return rv;
}
static struct var * find_item( char *name , int first_blank )
/*
* searches table for an item
* returns ptr to struct or NULL if not found
* OR if (first_blank) then ptr to first blank one
*/
{
int i;
int len = strlen(name);
char *s;
for( i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
{
s = tab[i].str;
if ( strncmp(s,name,len) == 0 && s[len] == '=' ){
return &tab[i];
}
}
if ( i < MAXVARS && first_blank )
return &tab[i];
return NULL;
}
void VLlist()
/*
* performs the shell's `set' command
* Lists the contents of the variable table, marking each
* exported variable with the symbol '*'
*/
{
int i;
for(i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
{
if ( tab[i].global )
printf(" * %s\n", tab[i].str);
else
printf(" %s\n", tab[i].str);
}
}
int VLenviron2table(char *env[])
/*
* initialize the variable table by loading array of strings
* return 1 for ok, 0 for not ok
*/
{
int i;
char *newstring;
for(i = 0 ; env[i] != NULL ; i++ )
{
if ( i == MAXVARS )
return 0;
newstring = malloc(1+strlen(env[i]));
if ( newstring == NULL )
return 0;
strcpy(newstring, env[i]);
tab[i].str = newstring;
tab[i].global = 1;
}
while( i < MAXVARS ){ /* I know we don't need this */
tab[i].str = NULL ; /* static globals are nulled */
tab[i++].global = 0; /* by default */
}
return 1;
}
char ** VLtable2environ()
/*
* build an array of pointers suitable for making a new environment
* note, you need to free() this when done to avoid memory leaks
*/
{
int i, /* index */
j, /* another index */
n = 0; /* counter */
char **envtab; /* array of pointers */
/*
* first, count the number of global variables
*/
for( i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
if ( tab[i].global == 1 )
n++;
/* then, allocate space for that many variables */
envtab = (char **) malloc( (n+1) * sizeof(char *) );
if ( envtab == NULL )
return NULL;
/* then, load the array with pointers */
for(i = 0, j = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
if ( tab[i].global == 1 )
envtab[j++] = tab[i].str;
envtab[j] = NULL;
return envtab;
}