操作系统实验二:shell 的简单实现

题目: 编写一个C语言程序作为Linux内核的Shell命令行解释程序,实现以下功能: (1)解析用户提交的命令行;按照环境变量搜索目录系统;执行命令。 (2)提供ls、mkdir rmdir、pwd、ps等内部命令。 (3)提供历史查询功能。如用户按下Ctr1+C,信号处理器将输出最近的10个命令列表。

直接贴实验代码,各位读者慢慢研读,最好不求甚解,否则会陷入好多我们不了解的知识问题里,比如代码里的获取ctrl+c的信号函数
先在ubantu下用mkdir命令创建一个目录文件(保持良好文件整理习惯)
这里创建了名为subshell的目录文件

setup.h

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include "queue.h"

#define MAX_LINE 80
void setup(char *inputBuffer, char *args[], int *background);

setup.c

#include "setup.h"

void setup(char *inputBuffer, char *args[], int *background){
    int length;//命令的字符数目
    int i;//循环变量
    int start;//命令的第一个字符位置
    int ct;//下一个参数存入args[]的位置

    ct = 0;

    /**
    *读入命令行字符,存入inputBuffer
    */
    length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
    start = -1;
    if(length == 0) exit(0);
    if(length < 0){//ctrl+c will read error, and return null
        args[0] = NULL;
        return;//输入ctrl+c时,会进入错误读取。从而退出setup函数,避免异常
    }

/*用户通过输入"rx" 可以运行之前10 个命令中的任何一个,其中"x" 为该命令的第一个字母。如果有个命令以"x" 开头,则执行最近的一个。同样,用户可以通过仅输入"r"来再次运行最近的命令*/
   if(inputBuffer[0]=='r'){
        char *str;
        if(inputBuffer[2]=='\n'||inputBuffer[2]=='\t'){
            if((str=gethistory(inputBuffer[1]))==NULL){ //如果没有以‘x'开头的命令,则gethist会返回NULL
                perror("error input command!");
                return;
            }
            else
                strcpy(inputBuffer,str); //放入inputBuffer以执行
        }
        else if(inputBuffer[1]=='\n'){ //当只有一个'r'输出时
             if((str=getnearhistory())==NULL){ //取最近的一条命令,如果没有命令,则getnearhist会返回NULL
                    perror("error input command!");
                    return;
                }
                else
                    strcpy(inputBuffer,str);
        }
        inputBuffer[strlen(str)]='\n'; //便于下面代码统一读取
        inputBuffer[strlen(str)+1]='\0'; //添加结束符
        length = strlen(str)+1;
    }

    //检查inputBuffer中每一个char
    for(int i = 0; i<length; i++ ){
        switch(inputBuffer[i]){
            case ' ':
            case '\t'://字符为参数的空格或者Tab
                if(start != -1){
                    args[ct] = &inputBuffer[start];
                    ct++;
                }
                inputBuffer[i] = '\0'; //设置c string 的结束符
                start= -1;
                break;

            case '\n': //命令行结束符
                if(start != -1){
                    args[ct] = &inputBuffer[start];
                    ct++;
                }
                inputBuffer[i] = '\0';
                args[ct] = NULL;
                break;

            default: //其他字符
               if(start == -1)  start = i;
               if(inputBuffer[i] == '&'){
                   *background = 1;
                   inputBuffer[i] = '\0';
               }
        }
    }
    add(inputBuffer);//添加历史记录
    args[ct] = NULL;//命令字符数 > 80
}

queue.h

#include <stdio.h>
#include <string.h>

#define MAX_LINE 80
#define HISTORYNUM 10
struct Queue{
    char history[HISTORYNUM][MAX_LINE];
    int front;
    int rear;
}queue;

void add(char *str);
void print();
char *gethistory(char index);
char *getnearhistory();
int getNum();

queue.c

#include "queue.h"

void add(char *str){ //添加命令进history,这里用循环队列实现
    if(queue.front==-1)
    {
        strcpy(queue.history[queue.rear],str);
        queue.front=queue.rear;
        return;
    }

    queue.rear =(queue.rear+1)%HISTORYNUM;
    strcpy(queue.history[queue.rear],str);
    if(queue.front==queue.rear) queue.front=(queue.front+1)%HISTORYNUM;
}
//打印历史命令
void print(){
    if(queue.front==-1){//空队列,无历史命令
        printf("No command!");
        return;
    }
    int index= queue.front;
    printf("\n");
    fflush(stdout);
    printf("%s\n",queue.history[index]); //先输出第一个历史命令,否则循环队列将无意义,当超出历史命令条数时,循环无法进入
    fflush(stdout);
    index=(index+1)%HISTORYNUM;
    while(index!=(queue.rear+1)&&(!(index==0&&queue.rear==9))){ //后面一个条件是防止这种情况进入死循环,因为,index永远不可能等于10
        printf("%s\n",queue.history[index]);
        fflush(stdout);
        index=(index+1)%HISTORYNUM;
    }
}

char *gethistory(char c){ //得到以‘x'开头的命令

    int temp=queue.front;
    while(temp!=queue.rear+1){
        if(queue.history[temp][0]==c)break; //找到就退出
        temp=(temp+1)%HISTORYNUM;
    }
    if(temp==queue.rear+1) return NULL; //搜索完一遍还是没有,就返回NULL
    return queue.history[temp];
}

char *getnearhistory(){ //返回最近的历史命令
    if(queue.front==-1) return NULL; //无历史命令
    else return queue.history[queue.rear];
}

int getNum(){  //得到历史命令的数目
    return (queue.rear+HISTORYNUM-queue.front+1)%HISTORYNUM;
}

shellMain.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <sys/shm.h>
#include "setup.h"

void handle_SIGINT();
int main(void){
    char inputBuffer[MAX_LINE];//这个缓存用来存放输入的命令
    int background;// == 1时,表示在后台运行命令,即在命令后加上“*”
    char *args[MAX_LINE/2+1];//命令最多40个参数
    pid_t pid;
    struct sigaction hander;

    queue.front = -1;
    queue.rear = 0;

    while(1){//程序在setup中正常结束
        background = 0;
        printf("COMMAND->");

        fflush(stdout);//输出
        setup(inputBuffer, args, &background);

        //这一步要创建子进程
        if((pid = fork()) == -1) { printf("Fork Error.\n"); }
        if(pid == 0) { execvp(args[0], args); exit(0); }
        if(background == 0) { wait(0); } //如果background为1,表示在后台运行,则父进程不用等子进程执行完,否则要wait

        /**
        *创建信号处理器,处理ctrl+c,+z等信号,这里想理解透可以自行学习hander,明白这里调用了系统函数就行
        */
        hander.sa_handler = (void (*)(int))handle_SIGINT;
        sigaction(SIGINT, &hander, NULL);

        /**
        *循环运行,直到收到ctrl+c获ctrl+z等信号
        */
    }
    return 0;
}

void handle_SIGINT(){
    print(); //打印历史命令
    signal(SIGINT, SIG_IGN);
}

gcc编译
这里写图片描述

验证
输入命令
这里写图片描述

按ctrl+c查看历史命令
这里写图片描述

使用’rx‘和’r’
这里写图片描述

实验完成!!!

猜你喜欢

转载自blog.csdn.net/qq_36172505/article/details/80372592