前言:
我们在上一章学习的进程的程序替换,这对于我们来说是一件重要的事,因为可以实现创建子进程执行别的程序进而实现进程程序的替换,那么现在我们就可以实现我们自主编写的shell,一个命令解释器了!
1、头文件以及宏还有内置参数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>
#define SIZE 520
#define NUM 64
// 利用宏定义保留最后一个“地址”
#define SkipPath(p) do{ p += (strlen(p) - 1); while(*p != '/') p--; }while(0)
char cwd[SIZE * 2]; // 记录绝对路径
char* aArgv[NUM]; // 记录用户输入命令,类似于main函数参数里的char* argv[]
int exit_code = 0; // 记录退出码
2、构建简单命令行
const char* GetUserName()
{
const char* username = getenv("USER");
if(username == NULL) return "None";
return username;
}
const char* GetHostName()
{
const char* hostname = getenv("HOSTNAME");
if(hostname == NULL) return "None";
return hostname;
}
const char* GetPwd()
{
const char* pwd = getenv("PWD");
if(pwd == NULL) return "None";
return pwd;
}
void CreateCommandLine()
{
const char* username = GetUserName();
const char* hostname = GetHostName();
const char* pwd = GetPwd();
char line[SIZE];
SkipPath(pwd);
snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, ((strlen(pwd) == 1) ? "/" : pwd));
printf("%s", line);
fflush(stdout);
}
3、获取用户命令字符串
char usercommand[SIZE];
int n = GetCommandLine(usercommand, sizeof(usercommand));
if(n <= 0) return 1;
int GetCommandLine(char command[], int n)
{
char* s = fgets(command, n, stdin); // 用fgets可以自动省略空格
if(s == NULL) return -1;
command[strlen(command) - 1] = '\0';
return strlen(command);
}
4、命令行字符串分割
void SplitCommandLine(char command[])
{
aArgv[0] = strtok(command, " "); // 利用strtok截取空格之前的字符串
int index = 1;
while((aArgv[index++] = strtok(NULL, " "))) ; // 第二次用strtok直接传递NULL参数即可自动截取
}
5、检查是否为内建命令
if(CheckBuildInCommand() == 1) continue;
int CheckBuildInCommand()
{
int flag = 0;
if(strcmp(aArgv[0], "cd") == 0)
{
flag = 1;
Cd();
}
else if(strcmp(aArgv[0], "echo") == 0 && strcmp(aArgv[1], "$?") == 0)
{
flag = 1;
printf("%d\n", exit_code);
exit_code = 0;
}
return flag;
}
因为我们一些系统调用的接口还不清楚,所以我们暂时用这些朴素的方法来判断是否为内建命令,像cd还有echo $?就是典型的内建命令。最难理解还是cd命令,因为cd是父进程在执行,所以你要是不判断内建命令的话是不会有反应的。
但是实现这个内建命令稍微有点复杂,首先你得找出来你要去往的路径,比如cd …
那么首先需要保存这个 …路径,再通过系统调用chdir直接将父进程的路劲改变。
以上是地址的改变,接下来难以理解的是环境变量的改变》
因为当前已经改变了路径,所以利用getcwd可以得到新地址,再将新地址以环境变量的方式进行保存,例如PWD = cwd这样,所以我们需要snprintf函数:
void Cd()
{
const char* path = aArgv[1];
if(path == NULL) path = GetHome();
chdir(path);
// change enviroment (honestly this part is not easy to understand)
char temp[SIZE * 2];
getcwd(temp, sizeof(temp));
snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
putenv(cwd);
}
6、执行命令
void ExecuteCommandLine()
{
pid_t id = fork();
if(id == 0)
{
//child
execvp(aArgv[0], aArgv);
exit(errno);
}
//father
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
exit_code = WEXITSTATUS(status);
if(exit_code != 0) printf("%s:%s:%d\n", aArgv[0], strerror(exit_code), exit_code);
}
}
if(rid > 0)
{
exit_code = WEXITSTATUS(status);
if(exit_code != 0) printf(“%s:%s:%d\n”, aArgv[0], strerror(exit_code), exit_code);
}
}
执行命令就还好,因为这和我们上文讲过的进程程序替换类似,在这里我就不过多赘述。