这一周的小项目是实现一个自己的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;
}