您的位置:首页 > 运维架构 > Shell

用c实现的简单linux shell

2015-06-25 20:10 483 查看
直接上代码吧!注释写的很清晰。

首先头文件:

// 头文件

#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;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: