[Linux]自我编写shell(命令行解释器)

如果一个命令行是./exec ls -a -l 

那么这个进程替换从ls开始执行。

 如果我们去掉./exec,那么我们是不是就可以自己写一个shell?

 用myshell替换mychild

  • 我们来用代码试一下。 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#define NUM 10
char  linecommand[NUM];
int main()
{
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);//减1是为了给'\0'留位置
  assert(s!=NULL);
  (void)s;
  printf("test:%s\n",s);
  return 0;
}

结果

 我们发现,中间多了一行,原因是我们在fgets输入时,输入里面有会车,(C语言中的gets不会读取回车,回车会留在缓冲区)再加上printf后的\n,就多出一行。

 

解决方式如下。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#define NUM 10
char  linecommand[NUM];
int main()
{
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  (void*)s;
    linecommand[strlen(linecommand)-1]=0;
  printf("test:%s\n",linecommand);
  return 0;
}

 

输入到linecommand时,输入几个字符,linecommand的长度要比输入字符数量多1,因为我们一定会输入回车,回车算长度。


获得命令行后,我们还需要对命令行字符串进行切割,以便实现对应的功能。 

我们创建一个指针数组,让每一个数组元素存一个命令的指针。

而获取每一个命令的指针,我们需要字符串切割的库函数strtok。

代码实现如下。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char  linecommand[NUM];
char *myargv[OPT_NUM];//指针数组
int main()
{
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  linecommand[strlen(linecommand)-1]=0;
  (void)s;
  myargv[0]=strtok(linecommand," ");
  int i=1;
  while(myargv[i++]=strtok(NULL," "));//如果没有子串了,strtok返回NULL,恰好我们需要myargv[end]==NULL
#ifdef DEBUG 
  for(int i=0;myargv[i];i++)
  {
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  return 0;
}

定义DEBUG在Makefile文件中这样定义。

myshell:myshell.c 
	gcc  -o myshell  myshell.c -std=c99 -DDEBUG 


结果

 前面添加#可以注释掉DEBUG


 

 在外面添加while(1)循环,来获得一下效果。

 下面就是我们自己写的一个shell例子的代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char  linecommand[NUM];
char *myargv[OPT_NUM];//指针数组
int main()
{
  while(1)
  {
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  linecommand[strlen(linecommand)-1]=0;
  (void)s;
  myargv[0]=strtok(linecommand," ");
  int i=1;
  while(myargv[i++]=strtok(NULL," "));
#ifdef DEBUG 
  for(int i=0;myargv[i];i++)
  {
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  pid_t id=fork();
  assert(id!=-1);
  if(id==0)
  {
  execvp(myargv[0],myargv);
  exit(-1);
  }
  waitpid(id,NULL,0);
}
  return 0;
}

代码实现就是这样的,我们直接输入命令就会执行对应的命令。

为了让ls时,能让目录带颜色,可以这么做,看下面。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char  linecommand[NUM];
char *myargv[OPT_NUM];//指针数组
int main()
{
  while(1)
  {
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  linecommand[strlen(linecommand)-1]=0;
  (void)s;
  myargv[0]=strtok(linecommand," ");
  int i=1;
  if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
  {
    myargv[i++]=(char*)"--color=auto";
  }
  while(myargv[i++]=strtok(NULL," "));
  
#ifdef DEBUG 
  for(int i=0;myargv[i];i++)
  {
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  pid_t id=fork();
  assert(id!=-1);
  if(id==0)
  {
  execvp(myargv[0],myargv);
  exit(-1);
  }
  waitpid(id,NULL,0);
}
  return 0;
}


 文件创建好了,没有指明在那个路径创建,默认在当前路径。


ls/proc是一个内存级的查看进程的方式,会形成一个以进程pid为目录的目录,里面包含进程的所有属性。

 

 exe指的是当前进程执行的是磁盘中该路径下的哪个程序。

 cwd指的是当前进程的工作目录。

 当前路径默认指的是当前进程的工作目录。即cwd。


 chdir可以更改工作路径。

#include<stdio.h>
#include<unistd.h>
int  main()
{
  chdir("/home/rhl/106-test");
  while(1)
  {
    printf("我是一个子进程:%d\n",getpid());
    sleep(1);      
  }
  return 0;
}


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char  linecommand[NUM];
char *myargv[OPT_NUM];//指针数组
int main()
{
  while(1)
  {
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  linecommand[strlen(linecommand)-1]=0;
  (void)s;
  myargv[0]=strtok(linecommand," ");
  int i=1;
  if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
  {
    myargv[i++]=(char*)"--color=auto";
  }
  while(myargv[i++]=strtok(NULL," "));
  
#ifdef DEBUG 
  for(int i=0;myargv[i];i++)
  {
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  pid_t id=fork();
  assert(id!=-1);
  if(id==0)
  {
  execvp(myargv[0],myargv);
  exit(-1);
  }
  waitpid(id,NULL,0);
}
  return 0;
}

为什么自己写的shell,cd到上级目录后,pwd没变呢。

 当前进程的工作目录,只属于当前的这个进程,我们fork子后,让子进程执行cd命令,子进程也有自己的工作目录,此时cd,更改的是子进程的目录,子进程执行完,父进程不受影响,此时我再输入pwd,因为子进程是从父进程重新创建,重新拷贝的,所以子进程工作目录并没有改变,pwd后还是原先的目录。

我们应该这样改。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char  linecommand[NUM];
char *myargv[OPT_NUM];//指针数组
int main()
{
  while(1)
  {
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  linecommand[strlen(linecommand)-1]=0;
  (void)s;
  myargv[0]=strtok(linecommand," ");
  int i=1;
  if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
  {
    myargv[i++]=(char*)"--color=auto";
  }
 while(myargv[i++]=strtok(NULL," "));   
  if(myargv[0]!=NULL&&strcmp(myargv[0],"cd")==0)
  {
   if(myargv[1]!=NULL)
     chdir(myargv[1]);
   continue;
  }
  
#ifdef DEBUG 
  for(int i=0;myargv[i];i++)
  {
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  pid_t id=fork();
  assert(id!=-1);
  if(id==0)
  {
  execvp(myargv[0],myargv);
  exit(-1);
  }
  waitpid(id,NULL,0);
}
  return 0;
}

 像这种不需要子进程进行,而是由shell执行的目录,我们叫内建/内置指令。

  • 写一个echo内置指令
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char  linecommand[NUM];
char *myargv[OPT_NUM];//指针数组
int main()
{
  int  lastcod;
  int lastsig;
  while(1)
  {
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  char* s=fgets(linecommand,sizeof(linecommand)-1,stdin);
  assert(s!=NULL);
  linecommand[strlen(linecommand)-1]=0;
  (void)s;
  myargv[0]=strtok(linecommand," ");
  int i=1;
  if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
  {
    myargv[i++]=(char*)"--color=auto";
  }
 while(myargv[i++]=strtok(NULL," "))   
  if(myargv[0]!=NULL&&strcmp(myargv[0],"cd")==0)
  {
   if(myargv[1]!=NULL)
     chdir(myargv[1]);
   continue;
  }
  if(myargv[0]!=NULL&&myargv[1]!=NULL&&strcmp(myargv[0],"echo")==0)
  {
    if(strcmp(myargv[1],"$?")==0)
    {
      printf("%d %d\n",lastcod,lastsig);
    }
    else
    {
      printf("%s\n",myargv[1]);
    }
    continue;
  }
#ifdef DEBUG 
  for(int i=0;myargv[i];i++)
  {
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  pid_t id=fork();
  assert(id!=-1);
  if(id==0)
  {
  execvp(myargv[0],myargv);
  exit(-1);
  }
  int status=0;
  waitpid(id,&status,0);
  assert(id>0);
  (void)id;
  lastcod=((status>>8)&0xFF);
  lastsig=((status)&0x7F);
  }
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南种北李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值