#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <sys/types.h>
#include <wait.h>
#define MAX_LINE 80
#define MAX_CMDS_CNT 12//history保存10条命令
typedef struct History {
char command_line[MAX_LINE];
int usage_cnt;
} history;
history h_lst[MAX_CMDS_CNT];
int head = 0, tail = 0;
// head指向循环队列首部,tail指向队列尾部的下一个编号
//故判断Head和tail的值,可以得出队列是否为空。
int cmd_cnt = 0;//此变量代表所有保存过的命令的个数,可以大于10
int should_run = 1;
void save_command(char *command_buffer){
strcpy(h_lst[tail].command_line, command_buffer);
h_lst[tail].usage_cnt ++;
tail = (tail + 1) % MAX_CMDS_CNT;//MAX_CMDS_CNT参加运算, 防止tail = 0时, tail - 1 = -1,负数索引
if(tail == head) head = (head + 1) % MAX_CMDS_CNT;//调整链表首部
//printf("%d %d\n",head,tail);
cmd_cnt++;
}
void delete_last_elem(){
if(head == tail)
return ;
tail = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
if(cmd_cnt >= 10){
if(tail == head) head = (head + 1) % MAX_CMDS_CNT;//调整链表首部
}
cmd_cnt --;
}
void display_commands_history(){
if(head == tail) {
printf("No commands in history.\n");
return ;
}
//从队尾,往队首遍历
int i = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
int endi = (head - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
//printf("%d %d\n",i,endi);
int t_cnt = cmd_cnt;
while(i != endi) {//由于条件原因,MAX_CMDS_CNT加二
printf("%3d %s\n", t_cnt--, h_lst[i].command_line);
i = (i - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
}
}
int process_command(char command_buffer[], char *args[], int *background){
save_command(command_buffer);
char *delim = " \r\t\n";
int tcnt = 0;
args[tcnt++] = strtok(command_buffer, delim);
//分解字符串,command_buffer为要被分解的字符串,delim为用作分隔符的字符(可以是一个,也可以是集合),该函数返回被分解的第一个子字符串,若无可检索的字符串,则返回空指针
while(args[tcnt++] = strtok(NULL, delim)) ;//不需要再传入command_buffer,strtok会持续分割之前传入的command_buffer
//当args[n]为null时退出循环,所以数组中最后一个值为null,即args[tcnt - 1]为null,然后tcnt自加
if(strcmp(args[tcnt-2], "&") == 0) {
args[tcnt-2] = NULL;
*background = 1;
}
if(strcmp(args[0], "!!") == 0 && args[1] == NULL) {
delete_last_elem();//删掉保存好的'!!'命令
if(cmd_cnt == 0) {
printf("No commands in history!\n");
return 0;
}
else{
int indx = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
strcpy(command_buffer, h_lst[indx].command_line);
return process_command(command_buffer, args, background);
}
}
// N should [cmd_cnt - MAX_CMDS_CNT + 1, cmd_cnt] and > 0.
if(args[0][0] == '!' && args[1] == NULL) {
delete_last_elem();
int is_int = 1;
for(int j = 1; args[0][j]; ++j) {//判断参数是否为数字
if(!isdigit(args[0][j])) {
is_int = 0;
break;
}
}
if(is_int) {
int ret_n = atoi(&args[0][1]);//把字符串nptr转换为int
int min_cmd_ind = 1 > cmd_cnt - MAX_CMDS_CNT + 1 ? 1 : cmd_cnt - MAX_CMDS_CNT + 1;
int max_cmd_ind = cmd_cnt;
if( ! (ret_n >= min_cmd_ind && ret_n <= max_cmd_ind) ) {
printf("No such command in history.\n");
return 0;
}
int offset = (cmd_cnt - ret_n);
int indx = (tail - 1 - offset + MAX_CMDS_CNT) % MAX_CMDS_CNT;
strcpy(command_buffer, h_lst[indx].command_line);
return process_command(command_buffer, args, background);
}
}
if (strcmp(args[0], "exit") == 0 && args[1] == NULL) {
should_run = 0;//结束父进程
}
else if (strcmp(args[0], "history") == 0 && args[1] == NULL) {
delete_last_elem();
}
return 1;
}
int get_command(char command_buffer[], char *args[], int *background){
int length;
length = read(STDIN_FILENO, command_buffer, MAX_LINE);
//STDIN_FILENO:接收键盘的输入,MAX_LINE是请求读取的最大字节数,读上来的数据保存在缓冲区command_buffer中,同时文件的当前读写位置向后移。
//read():成功则返回读取的字节数,出错则返回-1并设置errno,如果在调read()之前已到达文件末尾,则返回0
if (length == 1) // \n,说明只按了一个回车键,则重新接收命令
return 0;
if (length < 0) {
printf("Command reading failure!\n");
exit(-1);//退出当前运行的程序,并将-1返回给主调进程
}
command_buffer[length-1] = 0;//将回车替换为'\0',表示字符串结束
return process_command(command_buffer, args, background);
}
int execute_command(char *args[], int *background)
{
if (strcmp(args[0], "history") == 0) {
display_commands_history();
return 1;
}
if(strcmp(args[0], "exit") == 0) {
exit(0);//退出的是子进程
}
execvp(args[0], args);
}
int main(int argc, char *argv[]){
char command_buffer[MAX_LINE];//存储输入的整个字符串
int background;//判断是否输入了'&'符号,若在命令最后使用了"&"符号,父进程与子进程并发执行,即父进程是否等待子进程
char *args[MAX_LINE / 2 + 1];//command line arguments
pid_t pid, tpid;
while (should_run) {
background = 0;
printf("osh>");
fflush(stdout);//调用printf()时,输出的结果一般会被标准库缓存起来,可能不会及时打印写出到输出设备上面,此时就可以用fflush(stdout)强制把缓存内容进行输出
int ret_flag = get_command(command_buffer, args, &background);
if (ret_flag > 0) {
pid = fork();
if (pid < 0) {
printf("Failed to fork a process!\n");
exit(1);
}
else if (pid == 0) {
//child process
if (execute_command(args, &background) == -1)
printf("Failed to execute the command!\n");
printf("child over!\n");
break;
}
else {
printf("%d\n",background);
// father process
if (background == 0) {
//解决输入&之后,父进程先于子进程结束的问题
//原因:前一个并发执行时,子进程执行完exit后,由于父进程是并发执行,因此并没有调用wait进行“收尸”
//所以下一次非并发执行时,父进程wait“收尸”的是上一次并发执行子进程的“尸体”
//以往,会导致整个程序的父进程总是在“收尸”上一个子进程
while(wait(NULL) != pid);
}
printf("father over!\n");
}
}
}
return 0;
}