题目: 编写一个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’
实验完成!!!