本博客不再更新,更多精彩內容請訪問我的獨立博客
直接上代碼吧!註釋寫的很清晰。
首先頭文件:
// 頭文件
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAX_PROMPT 1024//提示符最大長度
#define MAXLINE 4096
#define MAXARG 20//參數最多個數
struct parse_info;
struct passwd *pwd;
char *buffer;
void terminal_prompt(char*);//終端提示符函數
int read_command(char **,char **,char*);//讀取命令函數
int builtin_command(char *,char **);//內建命令函數
int parsing(char **,int,struct parse_info *);//語法分析函數
void execute(void);//執行函數
void child_process_handler(int sig);//處理後臺運行的進程
#define STRUCT_PARSE_INFO
#define BACKGROUND 1 //後臺執行標誌位
#define IN_REDIRECT 2 //輸入重定向標誌位
#define OUT_REDIRECT 4 //輸出重定向標誌位
#define IS_PIPED 8//管道標誌位
#define TRUE 1
#define MAXPIDTABLE 1024
pid_t child_process_pid[MAXPIDTABLE];//子進程號數組
struct parse_info
{
int flag; //表明使用了哪些功能的標誌位
char* in_file;//輸入重定向的文件名
char* out_file;//輸出重定向的文件名
char* command2;//命令2
char** parameters2;//命令2的參數表
};
main函數:
//mysh主程序
#include "myshell.h"
int main() {
int i;
//初始化 child_process_pid
for(i=0;i<MAXPIDTABLE;i++)
child_process_pid[i] = 0;
//printf("mysh starting...\n");
execute();
return 0;
}
終端提示符函數:
//終端提示符函數
#include "myshell.h"
const int max_name_len = 256;
const int max_path_len = 1024;
void terminal_prompt(char *prompt)
{
extern struct passwd *pwd;//用戶賬號信息文件
char hostname[max_name_len];//主機名
char pathname[max_path_len];//路徑
int length;
pwd = getpwuid(getuid());//獲得用戶信息
getcwd(pathname,max_path_len);//獲得路徑
gethostname(hostname,max_name_len);
sprintf(prompt,"【mysh】%s@%s:",pwd->pw_name,hostname);//將用戶名和主機名添加到提示符
length = strlen(prompt);
if(strlen(pathname) < strlen(pwd->pw_dir) || strncmp(pathname,pwd->pw_dir,strlen(pwd->pw_dir))!=0)
sprintf(prompt+length,"%s",pathname);
else
sprintf(prompt+length,"~%s",pathname+strlen(pwd->pw_dir));//用戶路徑用~代替,當前路徑在用戶路徑下
length = strlen(prompt);
if(geteuid()==0)//root用戶
sprintf(prompt+length,"# ");
else//普通用戶
sprintf(prompt+length,"$ ");
return;
}
讀命令函數://讀入命令函數
//從用戶輸入中讀取命令和參數,分別放入command[]和parameters[][]中,作爲exec族函數執行。
#include "myshell.h"
int read_command(char **command,char **parameters,char *prompt)
{
printf("%s",prompt);//輸出終端提示符
if(fgets(buffer,MAXLINE,stdin) == NULL)//從鍵盤讀入字符存入buffer
{
printf("\n");
exit(0);
}
if(buffer[0] == '\0')
return -1;
char *pStart,*pEnd;
int count = 0;
int isFinished = 0;
pStart = pEnd = buffer;
while(isFinished == 0)
{
while((*pEnd == ' ' && *pStart == ' ') || (*pEnd == '\t' && *pStart == '\t'))//忽略空格和tab字符
{
pStart++;
pEnd++;
}
if(*pEnd == '\0' || *pEnd == '\n')//到字符結尾
{
if(count == 0)//讀入的合法字符爲0
return -1;
break;
}
while(*pEnd != ' ' && *pEnd != '\0' && *pEnd != '\n')
pEnd++;
if(count == 0)
{
char *p = pEnd;
*command = pStart;//命令存入command
while(p!=pStart && *p !='/')
p--;
if(*p == '/')//轉義字符
p++;
parameters[0] = p;//參數存入parameters
count += 2;
}
else if(count <= MAXARG)
{
parameters[count-1] = pStart;
count++;
}
else
{
break;
}
if(*pEnd == '\0' || *pEnd == '\n')//到輸入字符末尾
{
*pEnd = '\0';
isFinished = 1;
}
else
{
*pEnd = '\0';
pEnd++;
pStart = pEnd;
}
}
parameters[count-1] = NULL;
return count;
}
語法分析函數:
//語法分析函數
//主要處理後臺執行,重定向,管道功能實現
#include "myshell.h"
int parse_info_init(struct parse_info *info)
{
info->flag = 0;//表明使用了哪些功能的標誌位
info->in_file = NULL;//輸入重定向的文件名
info->out_file = NULL;//輸出重定向的文件名
info->command2 = NULL;//命令2
info->parameters2 = NULL;//命令2的參數表
return 0;
}
int parsing(char **parameters,int ParaNum,struct parse_info *info)
{
int i;
parse_info_init(info);
if(strcmp(parameters[ParaNum-1],"&") ==0)//後臺執行
{
info->flag |= BACKGROUND;
parameters[ParaNum-1] = NULL;
ParaNum--;
}
for(i=0;i<ParaNum;)
{
if(strcmp(parameters[i],"<")==0)//輸入重定向
{
info->flag |= IN_REDIRECT;
info->in_file = parameters[i+1];//輸入重定向的文件名
parameters[i] = NULL;
i+=2;
}
else if(strcmp(parameters[i],">")==0)//輸出重定向
{
info->flag |= OUT_REDIRECT;
info->out_file = parameters[i+1];//輸出重定向的文件名
parameters[i] = NULL;
i+=2;
}
else if(strcmp(parameters[i],"|")==0)//管道
{
char* pCh;
info->flag |= IS_PIPED;
parameters[i] = NULL;
info->command2 = parameters[i+1];
info->parameters2 = ¶meters[i+1];
//檢查parameters2[0]中是否有轉義字符
for(pCh = info->parameters2[0]+strlen(info->parameters2[0]);pCh!=&(info->parameters2[0][0]) && *pCh!='/';pCh--);
if(*pCh == '/')
pCh++;
info->parameters2[0] = pCh;
break;
}
else
i++;
}
return 1;
}
附加命令函數:
//內建命令函數
//實現了exit、cd命令
#include "myshell.h"
int builtin_command(char *command, char **parameters)
{
extern struct passwd *pwd;
if(strcmp(command,"exit")==0 || strcmp(command,"quit")==0){//實現exit和quit//都是退出命令
exit(0);
}
else if(strcmp(command,"help") == 0)
{
printf("copyright reserved by weiqianghu.\n");
return 1;
}
else if(strcmp(command,"cd")==0)//實現cd命令
{
char *cd_path = NULL;
if(parameters[1][0] == '~')//如果是根目錄
{
cd_path = malloc(strlen(pwd->pw_dir)+strlen(parameters[1]));//分配內存
if(cd_path == NULL)
{
printf("cd:malloc failed.\n");//內存分配失敗
}
else{
strcpy(cd_path,pwd->pw_dir);
strncpy(cd_path+strlen(pwd->pw_dir),parameters[1]+1,strlen(parameters[1]));
}
}
else//非根目錄
{
cd_path = malloc(strlen(parameters[1]+1));//分配內存
if(cd_path == NULL)
{
printf("cd:malloc failed.\n");//內存分配失敗
}
else
strcpy(cd_path,parameters[1]);
}
if(chdir(cd_path)!= 0)
printf("-myshell: cd: %s:%s\n",cd_path,strerror(errno));
else
return 1;
free(cd_path);//釋放分配的內存空間
}
return 0;
}
執行函數:// mysh執行函數
#include "myshell.h"
void execute(void)
{
int status,i;
char *command = NULL;//命令
char **parameters;//參數
int ParaNum;//參數個數
char prompt[MAX_PROMPT];//提示
struct parse_info info;
pid_t ChdPid,ChdPid2;
parameters = malloc(sizeof(char *)*(MAXARG+2));
buffer = malloc(sizeof(char) * MAXLINE);
if(parameters == NULL || buffer == NULL)//內存分配失敗
{
printf("mysh error:malloc failed.\n");
return;
}
if(signal(SIGCHLD,child_process_handler) == SIG_ERR)//父進程調用將SIGCHLD綁定到sig_handler()函數,這樣fork出來的子進程在終止時會自動清理掉
perror("signal() error");
while(TRUE)
{
int pipe_fd[2],in_fd,out_fd;
terminal_prompt(prompt);//終端提示符函數
ParaNum = read_command(&command,parameters,prompt);//讀入命令
if(-1 == ParaNum)
continue;
ParaNum--;//命令數減一
parsing(parameters,ParaNum,&info);//對於後臺執行、管道、重定向的初始化操作
if(builtin_command(command,parameters))//內建命令
continue;
if(info.flag & IS_PIPED) //管道
{
if(pipe(pipe_fd)<0)//pipe_fd[0]:讀管道;pipe_fd[1]:寫管道
{
printf("mysh error:pipe failed.\n");
exit(0);
}
}
if((ChdPid = fork())!=0) //mysh主進程
{
if(info.flag & IS_PIPED)
{
if((ChdPid2=fork()) == 0) //要求管道進程必須爲mysh進程的子進程
{
close(pipe_fd[1]);
close(fileno(stdin));
dup2(pipe_fd[0], fileno(stdin));
close(pipe_fd[0]);
if(execvp(info.command2,info.parameters2)==-1)
printf("%s\n",strerror(errno));
}
else
{
close(pipe_fd[0]);
close(pipe_fd[1]);
waitpid(ChdPid2,&status,0); //wait command2
}
}
if(info.flag & BACKGROUND)//後臺執行
{
printf("後臺進程pid:%u\n",ChdPid);
int i;
for(i=0;i<MAXPIDTABLE;i++)
if(child_process_pid[i]==0){
child_process_pid[i] = ChdPid; //存儲子進程號
break;
}
if(i==MAXPIDTABLE)
printf("Too much background processes\nThere will be zombine process");
}
else
{
waitpid(ChdPid,&status,0);//等待命令1結束
}
}
else //mysh的子進程
{
if(info.flag & IS_PIPED) //命令2不爲空
{
if(!(info.flag & OUT_REDIRECT)) // 僅管道
{
close(pipe_fd[0]);
close(fileno(stdout));
dup2(pipe_fd[1], fileno(stdout));
close(pipe_fd[1]);
}
else //輸出重定向和管道
{
close(pipe_fd[0]);
close(pipe_fd[1]);//關閉管道,發送一個EOF信號給command2
if(info.flag & OUT_REDIRECT)
out_fd = open(info.out_file, O_WRONLY|O_CREAT|O_TRUNC, 0666);
else
out_fd = open(info.out_file, O_WRONLY|O_APPEND|O_TRUNC, 0666);
close(fileno(stdout));
dup2(out_fd, fileno(stdout));
close(out_fd);
}
}
else
{
if(info.flag & OUT_REDIRECT) // 僅輸出重定向
{
out_fd = open(info.out_file, O_WRONLY|O_CREAT|O_TRUNC, 0666);
close(fileno(stdout));
dup2(out_fd, fileno(stdout));
close(out_fd);
}
}
if(info.flag & IN_REDIRECT)//輸入重定向
{
in_fd = open(info.in_file, O_CREAT |O_RDONLY, 0666);//0666表示, 擁有者,組,其他人都擁有可讀可寫權限
close(fileno(stdin));
dup2(in_fd, fileno(stdin));
close(in_fd);
}
if(execvp(command,parameters)==-1)
printf("%s\n",strerror(errno));
}
}
free(parameters);//釋放parameters
free(buffer);//釋放buffer
}
後臺進程處理函數:
//處理後臺運行進程函數
#include "myshell.h"
void child_process_handler(int sig)//用於清除後臺運行的子進程,以防其變成殭屍進程
{
pid_t pid;
int i;
for(i=0;i<MAXPIDTABLE;i++)
if(child_process_pid[i] != 0) //僅處理後臺執行命令
{
pid = waitpid(child_process_pid[i],NULL,WNOHANG);
if(pid > 0)//正常返回
{
printf("\n後臺進程[%d]已完成.\n",pid);
child_process_pid[i] = 0; //清除
}
else if(pid < 0)//出錯
{
if(errno != ECHILD)
perror("waitpid error");
}
}
return;
}