实现自己的myshell

这一周的小项目是实现一个自己的she’ll:实现输入输出重定向,管道,支持shell的内置cd命令,支持后台运行,实现tab补全和历史记录上下翻,ctrl+c不能中断程序,设置环境变量(让其可以像bash,zsh一样运行)

各点的实现总结如下:

1.重定向:输出重定向,创建你想输出到的文件,然后用dup2(old_fd,new_fd)这个函数,将其的文件描述符置为1,因为
标准输入(stdin):代码为0
标准输出(stdout):代码为1
标准错误输出(stdeer):代码为2

所以,我们让这个我们创建的文件占据了标准输出的位置,本来要输出到屏幕上的信息就会输出到这个文件里,而输入重定向和管道思路基本相同

2.管道:管道我们将输入的数据存入文件(描述符改为0),在输出到制定文件(只要把该文件描述符置为1)

3.后台运行只要不让其父进程等待即可

4.而cd命令则是检测到有 cd 命令后在子函数或在main里用chdir函数更改当前工作目录

***5.***ctrl+c不能中断只要我们选择忽略这个强制终端的信号,不让我们的程序接收就好了 signal( SIGINT,SIG_IGN )

6.设置环境变量这条只需把我们的可执行文件放置在/bin目录下,因为执行命令时会循$PATH这个环境变量指向的目录找第一个能被执行的命令

7.至于实现命令补全和历史命令上下翻就需要用到一个外部库,没有的需要下载wget install readline-devel
我们实现补全和历史记录只需要用到其中两个函数,至于想了解更多关于readline,请自行百度…

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include <readline/readline.h>
#include <readline/history.h>
int main()
{
    char *buf=NULL;
    int len;
    while(1)
    {

    buf=(char *)malloc(sizeof(char)*256);
    buf=memset(buf,0,256);   
    buf=readline("");
    len=strlen(buf);
    printf("len = %d   buf = >%s<\n",len,buf);
    add_history(buf);
    }
    return 0;
}

(这是段测试,好让大家理解readline的使用)
readLine()在进行读取一行时,只有遇到回车(\r)或者换行符(\n)才会返回读取结果,这就是“读取一行的意思”,重要的是readLine()返回的读取内容中并不包含换行符或者回车符
并且,当realLine()读取到的内容为空时,并不会返回 null,而是会一直阻塞,只有当读取的输入流发生错误或者被关闭时,readLine()方法才会返回null
readline的引号里是打印出来的提示符不会计算进录入的数据里,可以什么都不加(当然也可以啥都加)
而add_history()函数会增加一条命令到历史记录
因为是外部库,记得编译的时候加上 -lreadline
(吐槽:其实我觉得这个readline比较玄学,会发生一些语法上来说没道理的bug,比如别人只用了这两个函数就能自动实现补全,我在例子上测试的时候也可以补全,挪到自己的代码上就不行了,还有个人用readline读取第一遍的命令会有乱码,但是就只有第一遍不对…接下来就好了)

还有上述的那些功能操作,最好在fork开辟的子进程里进行,子进程会继承父进程的数据和环境变量,却具有独立性,这样像改变文件描述符或者一些其他的,不会影响父进程(而开辟子进程后使用的那些exec族函数,大家可以去看我的上一篇博客~…)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<dirent.h>
#include<pthread.h>
#include<pwd.h>
#include<grp.h>
#include <readline/readline.h>
#include <readline/history.h>


#define normal              0   //一般的命令
#define out_redirect        1   //输出重定向
#define in_redirect         2   //输入重定向
#define have_pipe           3   //命令中有管道


void print();                        //打印提示符
void get_input(char *buf);         //得到输入的命令
void explain_input( char *buf,int *argcount,char arglist[][256] );      //对输入的命令进行解析
void do_cmd( int argcount,char arglist[][256] ); //执行命令
int find_command( char *command);     //查找命令中的可执行程序


int main( int argc,char **argv )
{
    signal( SIGINT,SIG_IGN );    //忽略中断信号
    int i,argcount=0; 
    char arglist[100][256];
    char **arg=NULL;
    char *buf=NULL;

    while(1)
    {
        //将buf所指空间清0
        buf=(char *)malloc(sizeof(char)*256);
        memset( buf,0,256 );

        print();
       // get_input( buf ); 

        buf=readline(" ");            //使用readline()

        int len=strlen(buf);          //判断是否输入了命令
        if( !len )
        {
            continue;
        }

        if( *buf )
        {
            add_history(buf);
        } 

        //若输入的命令为exit或logout则退出本程序
        if( strcmp( buf,"exit" ) == 0 || strcmp( buf,"logout" ) == 0 )
        {
            break;
        }
        for( i=0;i<100;i++ )
        {
            arglist[i][0]='\0';
        }
        argcount=0;

        explain_input( buf, &argcount,arglist );  //命令解析
        do_cmd(argcount,arglist);                 //命令执行
        if( buf != NULL )
        {
            free( buf );
            buf = NULL;
        }
    }

    exit(0);
}
void print()                    //打印提示符
{
    uid_t uid;
    struct passwd *pw;

    uid = getuid();
    pw = getpwuid( uid );

    char *buf=NULL;
    buf=(char *)malloc(sizeof(char)*100);

    getcwd(buf,100);
    //来点颜色
    printf( "\033[;34m %s\033[0m",pw->pw_name);
    printf( "@lzj-ThinkPad-E565:" );
    printf( "\033[;36m%s\033[0m $ ",buf );

}

void explain_input( char *buf, int *argcount, char arglist[100][256] )  //解析buf中的命令 遇到\n结束
{
    char *p = buf;
    char *q = buf;
    int number = 0;

    while(1)
    {
        if( p[0] == '\0' )
        {
            break;
        }
        if( p[0] == ' ' )
            p++;
        else
        {
            q=p;
            number=0;
            while( (q[0] != ' ') && (q[0] != '\0') )
            {
                number++;
                q++;
            }
            strncpy( arglist[*argcount],p,number );
            arglist[*argcount][number] = '\0';
            *argcount += 1;
            p=q;
        }
    }
}

void do_cmd( int argcount,char arglist[][256] )
{
    int flag=0;
    int how=0;                         //用于指示命令中是否含有 >  <  |
    int background = 0;                     //标识命令中是否有后台运行标识符 &
    int status, i, fd;
    char *arg[argcount+1], *argnext[argcount+1], *file;
    pid_t pid; 

    //将命令取出
    for( i=0;i<argcount;i++ )
    {
        arg[i] = ( char * )arglist[i];
    }
    arg[argcount] = NULL;

    //查看命令行是否有后台运行符
    for( i=0;i<argcount;i++ )
    {
        if( strncmp( arg[i],"&",1 ) == 0 )
        {
            if( i == argcount-1 )             //&位置在命令最后
            {
                background=1;
                arg[argcount-1]=NULL;
                break;
            }
            else
            {
                printf( "Command error!\n" );
                return ;
            }
        }
    }

    if( strncmp( arg[0],"cd",2)==0 )  // cd命令
    {
        char temp[10] = "/home/lzj";
        if( arg[1] == NULL || !strcmp(arg[1],"~" ))
        {
            if( (chdir(temp) ))
            {
                printf("chdir ~ error     \n");
            }
            return ;
        }
        else
        {
            if(arg[1][0] == '~')
            {
                char test[20],test_1[20];
                strcpy(test,arg[1]);
                strncpy(test_1,test+1,sizeof(test)-1);
                strcat(temp,test_1);
                chdir(temp);
            }
            else if( chdir(arg[1]) )
            {
                printf( "error chdir" );
            }
            return ;
        }
    }

    for( i=0; arg[i] != NULL; i++ )
    {
        if( strcmp( arg[i],">" ) == 0 )  //命令中有输出重定向
        {
            flag++;
            how = out_redirect;
            if( arg[i+1] == NULL ) //如果 > 是最后一个
            flag++;
        }
        if( strcmp( arg[i],"<" ) == 0 )  //命令中有输入重定向
        {
            flag++;
            how = in_redirect;
            if( i == 0 )
            {
                flag++;
            }
        }
        if( strcmp( arg[i],"|" ) == 0 )  //命令中有管道
        {
            flag++;
            how=have_pipe;
            if( arg[i+1] == NULL )
            {
                flag++;
            }
            if( i == 0 )
            {
                flag++;
            }
        }
    }

    //flag>1 说明命令中含有多个>, <, | 符号,或者命令格式不对,对此我的shell表示力不从心
    if( flag>1 )
    {
        printf("Command error!\n");
        return ;
    }
    if( how == out_redirect )           //命令只含有一个输出重定向符号
    {
        for( i=0; arg[i] != NULL; i++ )
        {
            if( strcmp( arg[i],">" )==0 )
            {
                file = arg[i+1];
                arg[i] = NULL;
            }
        }
    }
    if( how == in_redirect )            //命令只含有一个输入重定向符号
    {
        for( i=0; arg[i] != NULL; i++ )
        {
            if( strcmp(arg[i],"<") == 0 )
            {
                file = arg[i+1];
                arg[i]=NULL;
            }
        }
    }
    if( how == have_pipe )              //命令中只有一个管道符号
    {
        //把管道后面的部分存入argnext,管道后面那部分是一个可执行的shell命令
        for( i=0; arg[i] != NULL; i++ )
        {
            if( strcmp( arg[i],"|" )== 0 )
            {
                arg[i] = NULL;
                int j;
                for( j=i+1; arg[i] != NULL; j++ )
                {
                    argnext[j-i-1] = arg[j];
                }
                argnext[j-i-1] = arg[j];   //存一个NULL
                break;
            }
        }
    }
    if( (pid = fork()) <0 ) //执行命令都在子进程中
    {
        printf( "process creation failed!\n" );
        return ;
    }
    switch( how )
    {
        case 0:
        {
            //pid=0说明是子进程,在子进程执行输入的命令,命令中不含>, <, |
            if( pid == 0 )
            {
                if( !(find_command( arg[0] )) )
                {
                    printf("%s :Command not found!\n",arg[0]);
                    exit(0);
                }
                execvp( arg[0],arg );
                exit(0);
            }
            break;
        }
        case 1:
        {
            //输入的命令中含有输出重定向
            if( pid == 0 )
            {
                if( (!find_command( arg[0] )) )
                {
                    printf( "%s : Command not found!\n",arg[0] );
                    exit(0);
                }
                fd = open( file,O_RDWR | O_CREAT | O_TRUNC, 0644 ); //可读可写,不存在创建,可写打开时,文件清空  0644的0表示十进制
                dup2( fd,1 );    //指定新文件描述符为1
                execvp( arg[0],arg );
                exit(0);
            }
            break;
        }
        case 2:
        {
            //输入的命令中含有输出重定向
            if( pid == 0 )
            {
                if( !(find_command(arg[0])) )
                {
                    printf( "%s : Command not find!\n",arg[0] );
                    exit(0);
                }
                fd = open( file,O_RDONLY ); //只读打开
                dup2( fd,0 );
                execvp( arg[0],arg );
                exit(0);
            }
            break;
        }
        case 3:
        {
            //输入的命令中含有管道符
            if( pid == 0 )
            {
                int pid2;
                int status2;
                int fd2;

                if( ( pid2=fork() )<0 )   //在子进程中在创建一个子进程
                {
                    printf( "process Creation failed!\n" );
                    exit(0);
                }
                else if( pid == 0 )
                {
                    if( !(find_command(arg[0])) )
                    {
                        printf( "%s : Command not found!\n",arg[0] );  //
                        exit(0);
                    }
                    fd2 = open( "/tmp/transfer",O_WRONLY | O_CREAT | O_TRUNC,0644 );
                    dup2( fd2,1 );
                    execvp( arg[0],arg );
                    exit(0);
                }

                if( waitpid( pid2,&status2,0 ) == -1 )
                {
                    printf( "wait for child process error!\n" );
                }
                if( !(find_command(arg[0])) )
                {
                    printf( "%s : Command not find!\n",arg[0] );
                    exit(0);
                }
                fd2 = open( "/tmp/transfer",O_RDONLY );
                dup2( fd2,0 );
                execvp( argnext[0],argnext );

                if( remove( "/tmp/transfer" ) )
                    printf( "remove error\n" );

                exit(0);
            }
            break;
        }

        default:
            break;
    }

    //若命令中有&,表示后台执行,父进程直接返回,不等待子进程结束
    if( background == 1 )
    {
        printf( "process id : %d\n",pid );
        return ;
    }
    if( waitpid( pid,&status,0 ) == -1 )
    {
        printf( "wait for child process error\n" );

    }
}


//查找命令中的可执行程序
int find_command( char *command )
{
    DIR *dp;
    struct dirent *dirp;
    char *path[]={ "./", "/bin", "/usr/bin", NULL };

    if ( strncmp( command,"./",2 ) == 0 )  //如果命令是./fork之类,使指针跳过目录指向命令
    {
        command=command+2;
    }

    //分别在当前目录,/bin和/usr/bin目录查找要执行的程序
    int i=0;
    while( path[i] != NULL )
    {
        if( (dp=opendir(path[i]) ) == NULL )
        {
            printf( "can not open /bin\n" );
        }
        while( (dirp=readdir(dp)) != NULL )
        {
            if( strcmp( dirp->d_name,command ) == 0 )
            {
                closedir( dp );
                return 1;
            }
        }
        closedir( dp );
        i++;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值