原理: shell是命令行解释器,当有命令需要执行时,shell创建子进程,让子进程执行命令,而shell只需等待子进程退出即可。
其中我们使用了下面这几个函数:
- 获取命令行(fgets函数)。
- 解析命令行(strtok分割字符串)。
- 创建子进程(fork函数)。
- 替换子进程(exec函数族)。
- 等待子进程退出(waitpid函数)
实现简易命令解释行的过程中我们遇到了一些问题:
cd,export等內键命令,在子进程下修改无法影响到bash,所以我们增加了函数来判断命令是否为內键命令,并判断其是否为空,使用chdir修改其所在路径通过argv[1]取出要到的路径。
但是我们发现如果进入的是.. 上级路径的话 就会变成下面这样
我们的目录名称直接变成了..,此时我们需要使用 snprintf函数 将temp写入pwd中。
以下为简易实现的代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
char *argv[MAX_ARGC];
char PWD[SIZE];
const char* HostName()
{
char *hostname=getenv("HOSTNAME");
if(hostname) return hostname;
else return "none";
}
const char* UserName()
{
char *hostname=getenv("USER");
if(hostname) return hostname;
else return "none";
}
const char* CurrentWorkDir()
{
char *hostname=getenv("PWD");
if(hostname) return hostname;
else return "none";
}
char *Home()
{
return getenv("HOME");
}
int Interactive(char out[],int size)
{
printf("[%s@%s %s]$",UserName(),HostName(),CurrentWorkDir());
fgets(out,size,stdin);
out[strlen(out)-1]=0;//'\0'
return strlen(out);
}
void Split(char in[])
{
int i=0;
argv[i++]=strtok(in,SEP);
while(argv[i++]=strtok(NULL,SEP));
}
void Execute()
{
pid_t id=fork();
if(id==0)
{
execvp(argv[0],argv);
exit(1);
}
pid_t rid=waitpid(id,NULL,0);
printf("run done,rid: %d\n",rid);
}
int BuildinCmd()
{
int ret=0;
//1.检测是否为内建命令 是1 否0
if(strcmp("cd",argv[0])==0)
{
ret=1;
char *target=argv[1];
if(!target) target=Home();
chdir(target);
char temp[1024];
getcwd(temp,1024);
snprintf(PWD,SIZE,"PWD=%s",temp);
putenv(PWD);
}
//2.执行
return ret;
}
int main()
{
//1.打印命令行提示符,获取用户输入的命令字符串
while(1)
{
char commandline[SIZE];
int n= Interactive(commandline,SIZE);
if(n==0) continue;
//2.对命令行进行切割
Split(commandline);
//3.处理内建命令
n=BuildinCmd();
if(n) continue;
//4.执行这个命令
Execute();
}
//int j=0;
//for(j=0;argv[j];j++)
//{
// printf("argv[%d]: %s\n",j,argv[j]);
//}
return 0;
}
Makefile文件的配置:
myshell:myshell.c
gcc -o $@ $^
.PHONY:clean
rm -f myshell
演示结果如下