(1)每次主进程在接收指令进行解释之前,调用waitpid(-1,NULL, WNOHANG);释放可能存在的执行完毕的子进程资源(不会等待);
(2)当输入一条指令后,将指令拆分成独立的符号;
(3)如果指令的最后一个符号是’&’,则(5)子进程执行指令的时候,父进程将不会wait()子进程执行完毕,继续接收下一条指令进行解释;
(4)History指令由主程序使用循环队列进行维护,最多只记录10条历史指令;
(5)如果是非history指令,fork()一个子进程,调用execvp()进行解释执行;
一、主程序实现代码:
/*
** Desc:
** simulate the shell interepter.
** a command line can only be up to 80 characters, the
** command line will be divided into different tokens
** stored into the 'args' array. Then call the function
** execvp() to execute the command in the child process.
** if the last token is '&', the parent process will
** not wait the child process to complete, just continue.
** Otherwise, the parent should wait for it.
**
** Bugs: many
** 1. when execute history command, maybe suffer infinite loop
** 2. some commands like 'cd', no effect
** 3. some shell syntax, can'te recognize, like '(ls -trl)'
** 4. more and more...
**
** Date: 2015-01-23
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<errno.h>
#include<unistd.h>
#include<sys/wait.h>
/*
** define a queue to manage the history commands,
** but only last 10 commands
*/
#include"queue.h"
#define MAX_TOKEN (MAX_LINE / 2 + 1)
#define _FL_ __FILE__, __LINE__
#define TRUE 1
#define FALSE 0
#define CLR_BUF while(getchar() != '\n') continue
/*
** split the command line into seperate tokens
*/
int token(char *str, char **args, int num);
int
main(void)
{
char *args[MAX_TOKEN]; /* command line tokens */
char *his_cmd; /* hold some history command fetched from the queue */
char cmd[MAX_LINE + 1]; /* record the command line */
int should_run = TRUE; /* flag to detemine when to exit program */
int is_wait = TRUE; /* whether wait for the child process */
int is_go = FALSE;
int token_num = 0; /* how many tokens */
int i = 0; /* just for loop */
int his_num; /* which history command */
pid_t pid = -1; /* record the child process identifier */
Qptr qptr; /* pointer to the queue, declared in queue.h */
/*
** allocate a queue to manage history commands
** and initialize it
*/
qptr = (Qptr)malloc(sizeof(Queue));
if(NULL == qptr)
{
fprintf(stderr, "[%s:%d]malloc error[%s]\n", _FL_, strerror(errno));
return 1;
}
init_queue(qptr);
/*
** the shell interpreter begin
*/
while(should_run)
{
memset(cmd, 0x00, sizeof(cmd));
memset(args, 0x00, sizeof(args));
/*
** waitpid
** recycle the posible zombie child process
** but it will not block to wait
** if this function doesn't exist, execut the command like:
** ls -trl &
** the child process will become zombie process and influence
** the coming process
*/
waitpid(-1, NULL, WNOHANG);
printf("osh>");
fflush(stdout);
/*
** After reading user input, the steps are:
** (0) preprocess, get the command line and split it
** into tokens
** (1) whether input the history command? if it is,
** fetch the old command, continue step 2; or just display
** the last commands and then go to (0).
** Otherwise, record the command into the queue, and
** go to step (2)
** (2) if input 'exit', go to (4)
** Otherwise, fork a child process using fork()
** (3) the child process will invoke execvp() to execute
** if command included &, parent will not wait(). Go to
** (0)
** (4) exit the program
*/
/*
** (0) get the command line up to 80 characters
*/
fgets(cmd, MAX_LINE, stdin);
if(cmd[strlen(cmd) - 1] != '\n')
{
fprintf(stderr, "[%s:%d]The command is too long\n", _FL_);
CLR_BUF;
continue;
}
/*
** (0) split the command line into tokens
*/
token_num = token(cmd, args, MAX_TOKEN);
if(-1 == token_num)
{
fprintf(stderr, "[%s:%d]token error\n", _FL_);
return 1;
}
for(i = token_num; i < MAX_TOKEN; i++)
args[i] = NULL;
/*
** (0) whether the parent process will wait for the child process
*/
if(token_num > 0 && 0 == strcmp(args[token_num - 1], "&"))
{
is_wait = FALSE;
args[token_num - 1] = NULL;
}
/*
** (1) whether input history command
*/
if(token_num > 0 && 0 == strcmp(args[0], "history"))
{
while(TRUE)
{
if(1 == token_num)
{
/*
** just show the old commands
*/
insert_queue(qptr, cmd);
display_queue(qptr);
is_go = FALSE;
break;
}
else if(2 == token_num)
{
/*
** fetch the history command, then execute it
*/
his_num = atoi(args[1]);
if(NULL != (his_cmd = fetch_queue(qptr, his_num)))
{
token_num = token(his_cmd, args, MAX_TOKEN);
if(-1 == token_num)
{
fprintf(stderr, "[%s:%d]token error\n", _FL_);
return 1;
}
for(i = token_num; i < MAX_TOKEN; i++)
args[i] = NULL;
if(token_num > 0 && 0 == strcmp(args[token_num - 1], "&"))
{
is_wait = FALSE;
args[token_num - 1] = NULL;
}
if(token_num > 0 && 0 != strcmp(args[0], "history"))
{
insert_queue(qptr, cmd);
is_go = TRUE;
break;
}
}
else
{
fprintf(stderr, "Not found\n");
is_go = FALSE;
break;
}
}
else
{
fprintf(stderr, "usage: history [record num]\n");
is_go = FALSE;
break;
}
}
/*
** get the final history command, continue executing
*/
if(!is_go)
continue;
}
else if(token_num > 0)
{
/*
** not history command, just record it
*/
insert_queue(qptr, cmd);
}
/*
** (2) whether input "exit"
*/
if(token_num > 0 && 0 != strcmp(args[0], "exit"))
{
/*
** not 'exit', fork a child process to execute
*/
pid = fork();
if(pid < 0)
{
fprintf(stderr, "[%s:%d]fork() err[%s]\n", _FL_, strerror(errno));
return 1;
}
else if(0 == pid) /* child process */
{
execvp(args[0], args);
break;
}
else /* parent process */
{
if(is_wait)
{
wait(NULL);
}
is_wait = TRUE;
}
}
else if(0 == token_num)
{
continue;
}
else
{
/*
** input 'exit', exit the program
*/
should_run = FALSE;
}
/*
** free the heap resource
*/
for(i = 0; i < token_num; i++)
free(args[i]);
}
return 0;
}
/*
** int token(char *str)
** operation:
** divide the command string into seperate tokens
** input:
** @str -- command string
** @args -- hold the tokens
** @num -- maximum tokens
** output:
** return the number of tokens
*/
int token(char *str, char **args, int num)
{
int is_space = TRUE;
int i = 0;
int j = 0;
int k = 0;
char tmp[MAX_LINE];
if(num <= 0)
{
fprintf(stderr, "[%s:%d]Illegal arguments num(> 0): %d\n", _FL_, num);
return 1;
}
while(str[i] != '\0')
{
if(isspace(str[i]))
{
if(!is_space) /* have fetched a token */
{
if(k >= num)
{
fprintf(stderr, "[%s:%d]Reach the mamximum tokens: %d\n", _FL_, num);
return -1;
}
/*
** generate a new token
*/
tmp[j] = '\0';
args[k] = (char *)malloc(MAX_LINE * sizeof(char));
if(NULL == args[k])
{
fprintf(stderr, "[%s:%d]malloc err[%s]\n", _FL_, strerror(errno));
return -1;
}
strcpy(args[k], tmp);
/*
** after handling the token, initialize the variables
*/
k++; /* next token */
j = 0; /* begin to fetch token */
}
is_space = TRUE;
}
else if(isprint(str[i]))
{
is_space = FALSE;
tmp[j++] = str[i];
}
else
{
fprintf(stderr, "[%s:%d]Illegal input: %03o\n", _FL_, str[i]);
return -1;
}
i++; /* next character */
}
return k;
}
二、history指令维护的队列接口声明:
/*
** queue.h
** declare a data structure to manage the history command
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define HIS_NUM 10
#define REL_NUM (HIS_NUM + 1)
#define MAX_LINE 80
typedef struct queue
{
char content[REL_NUM][MAX_LINE];
int bgn_index;
int end_index;
int count;
} Queue;
typedef Queue* Qptr;
void init_queue(Qptr qptr);
void insert_queue(Qptr qptr, char *cmd);
void display_queue(Qptr qptr);
char *fetch_queue(Qptr qptr, int his_num);
三、history指令维护的队列的接口实现:
/*
** queue.h
** declare a data structure to manage the history command
*/
#include"queue.h"
void init_queue(Qptr qptr)
{
qptr->bgn_index = 0;
qptr->end_index = 0;
qptr->count = 0;
}
void insert_queue(Qptr qptr, char *cmd)
{
strcpy(qptr->content[qptr->end_index], cmd);
qptr->end_index = (qptr->end_index + 1) % REL_NUM;
if(qptr->count < HIS_NUM)
qptr->count++;
if(qptr->end_index == qptr->bgn_index)
qptr->bgn_index = (qptr->bgn_index + 1) % REL_NUM;
}
void display_queue(Qptr qptr)
{
int i = 0;
int count = qptr->count;
if(qptr->end_index != qptr->bgn_index)
{
i = qptr->bgn_index;
while(i != qptr->end_index)
{
printf("%02d %s\n", count, qptr->content[i]);
HIS_NUM == i ? i = 0 : i++;
count--;
}
}
else
printf("No history command\n");
}
char *fetch_queue(Qptr qptr, int his_num)
{
int i = 0;
char *tmp = NULL;
if(his_num <= 0 || his_num > qptr->count)
{
fprintf(stderr, "Bad history record\n");
}
else
{
i = qptr->end_index;
if(i - his_num < 0)
i += HIS_NUM;
i -= his_num;
tmp = qptr->content[i];
}
return tmp;
}