用c實現的簡單linux shell

本博客不再更新,更多精彩內容請訪問我的獨立博客


直接上代碼吧!註釋寫的很清晰。

首先頭文件:

// 頭文件

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






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章