如果一个命令行是./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;
}