目录
引言
1、打印命令行提示符
2、获取命令
3、分析命令
4、执行命令
5、判断内建命令
cd
1、cd/cd ~
2、cd +路径
3、cd –
echo
6、判断是否为重定向
总结
引言
在前几篇文章中,我们深入学习了进程方面和基础IO的知识,为了让大家对这些知识有更好的理解,并且能融会贯通,今天我来手把手的带大家依据前几篇文章学习的知识来写一个简单的Shell。
编写这个程序要包的头文件如下,大家可以先行包好
#include <climits>
#include <cstdlib>
#include<ctype.h>
#include<iostream>
#include<cstdio>
#include <memory>
#include<pthread.h>
#include <sched.h>
#include<stdlib.h>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
1、打印命令行提示符
首先,一个shell,我们要有一个命令行提示符,我们先来看看一个命令行提示符都由什么部分组成,第一个就是我们当前使用用户的用户名ubuntu;后面的 “VM-0-17-ubuntu”表示我们的主机名,但是这个主机名经过了格式化的处理,所以这个部分我们就直接照抄就行;冒号后面表示我们当前所属的文件路径。知道了命令行提示符由什么组成后,我们可以开始手写自己的命令行提示符了。
![]()
之前我们讲过,我们的用户名和工作路径在环境变量里都可以被找到,它们的变量名称分别是 “USER”和 “PWD”,所以我们可以用系统调用来获取它们的值。使用getenv需要包头文件stdlib.h
#include<stdlib.h>
//获取用户名
const char* GETUSERNAME()
{
return getenv("USER");
}
//获取工作路径
const char* GETPWD()
{
return getenv("PWD");
}
获取到这些环境变量后,我们就可以创建我们自己的命令行提示符了,我们先用一个宏将整个提示符的格式固定下来,然后使用snprintf函数,这个函数的作用是可以将我们提供的字符串按一定格式固定和长度格式化到一个数组里,这样我们就完成了命令行提示符的格式化,直接打印该数组即可。使用宏的目的是方便展开和修改。
#define HOSTNAME "VM-0-17-ubuntu"
#define FORMAT "%s@%s:%s# " //宏定义输出格式
#define COMMAND_SIZE 1024 //宏定义命令行最大长度
void MakeCommandLine(char cmd_command[], int size)
{
//格式化命令行提示符
snprintf(cmd_command, size, FORMAT, GETUSERNAME(), HOSTNAME, GETPWD());
}
void PrintCommandLine()
{
//打印命令行提示符
char prompt[COMMAND_SIZE];
MakeCommandLine(prompt, sizeof(prompt));
printf("%s", prompt);
fflush(stdout); //立刻刷新缓冲区让提示符出现
}
2、获取命令
现在第一步打印命令行提示符以及完成了,那我们的下一步应该是什么?是不是应该获取用户输入的命令了,所以接下来我们来写获取用户命令方面的代码。
首先,获取用户输入了什么命令,我们不能再使用之前常用的scanf或cin这种函数了,因为这些函数在遇到空格时就会自动截断,而我们在输入命令时是会大量使用空格的。所以在这里,我们需要使用fgets这个函数,这个函数可以一次性获取用户一次输入的所有内容,检测到换行符才停止,所以最好使用这个函数。

我们需要传一个数组,一个整形和一个文件流,数组就用来存储用户输入的命令,整形表示输入命令的最大单位,因为我们是要获取用户输入的内容,所以文件流就写stdin标准输入流。其次fgets函数的返回值类型为char* 类型,我们可以接收这个char* 类型的指针来判断获取命令是否成功。再其次,因为这个函数是在检测到换行符时截断,所以我们还需要手动的把换行符这个位置置为0,并且在我们平常使用系统的shell时,如果一行什么都不输入直接回车就会不执行任何命令,并且重新打印一次命令行提示符,所以在把回车符置为0后我们还需要检查一下当前数组内是否有命令,如果没有命令代表用户没有输入,直接进入下一轮循环。所以函数的返回值我们使用bool类型,如果输入成功返回true,否则返回false。
所以除了完成获取命令的函数之外,我们还要写一个循环,我们的shell像系统的shell一样能循环进行。
#define COMMAND_SIZE 1024
bool GetCommandLine(char* out, int size)
{
char* com = fgets(out, size, stdin);
if (com == nullptr)
return false;
out[strlen(out) – 1] = 0; //去除\\n
if (strlen(out) == 0)
return false;
return true;
}
int main()
{
while(true)
{
//输出命令行提示符
PrintCommandLine();
//输入命令
char Command[COMMAND_SIZE]; //存储命令
if(!GetCommandLine(Command, COMMAND_SIZE)) //输入失败就重新循环
{
continue;
}
return 0;
}
3、分析命令
现在命令已经输入完毕了,那么下一步,显然就是分析命令了,用户在输入命令时有时不止输入命令本身,还要附带上一些选项,所以我们需要对用户输入的命令进行一次预处理,将命令以及各个选项划分开来。
首先我们要先定义两个全局变量,一个整形变量g_argc代表输入的命令和选项的个数,一个char* 类型变量g_argv用于存储命令。其次,我们要将各个命令和选项分开,分别存进g_argv中,我们平常在输入命令时都以空格作为分隔符,所以我们可以调用一个函数来帮我们进行划分,这个函数是strtok,它可以帮我们将一个字符串以我们设定的分隔符进行分隔,返回指向子串的指针,所以g_argv的类型为char*类型。
strtok这个函数默认只帮我们划分一次,如果后续还要继续划分的话,第一个参数就不能再传原先的指针,而是传一个空指针nullptr过去,strtok会记住上一次切割结束的位置,从该位置开始继续找。需要注意的一个问题是:当这个字符串已经被分隔完成,已经找不到更多子串时,它还会额外进行一次返回,返回一个空指针。所以实际上我们获取的指针数组里会多出一个空指针元素,我们需要将这个位置置为空,并让g_argc减减,否则会导致我们实际输入的命令比它记录的指令少一个的问题,在后续的函数中导致执行错误。
#define MAXRACE 128
int g_argc = 0;
char* g_argv[MAXRACE];
bool CommandTable(char* cmd_prompt)
{
#define SEP " " //利用宏定义方便我们将来可以方便的切换为其他分隔符
g_argc = 0;
g_argv[g_argc++] = strtok(cmd_prompt, SEP);
while(g_argv[g_argc++] = strtok(nullptr, SEP)); //直到最后一次返回nullptr循环结束
if(g_argv[g_argc] == nullptr) //去除尾部的空指针
g_argc–;
return g_argc > 0 ? true : false; //判断是否输入了命令,加强代码健壮性
}
虽然我们在获取命令函数中就已经检查了用户是否输入的问题,但是在这里我们最好还是再进行一次检查,这样可以加强我们代码的健壮性。
4、执行命令
分析完命令后下一步就是执行命令。我们先来思考一个问题:执行命令的这个过程需要我们的shell进程本身去执行吗?很明显是不是不需要,我们可以直接创建一个子进程,再利用进程程序替换让子进程去执行对应的命令。那我们应该选exec系列里的哪一个函数去进行进程替换呢?
我们可以明显的看到,使用execvp这个函数最合适,最符合我们获取的命令表,在传参时只要将g_argv[0],也就是命令名传过去,然后将整个g_argv数组传过去,不就完美符合这个函数的参数列表了吗?创建进程相信大家都还记得,直接使用fork来创建子进程就行了。
int Execute()
{
pid_t id = fork();
if (id == 0)
{
execv Passed as __status ;
exit(1); //进程切换失败就退出
}
pid_t rid = waitpid(id, nullptr, 0); //父进程等待子进程
(void)rid; //使用以下rid以防警告
return 0;
}
如果子进程切换失败,子进程就会走到exit(1)的位置,直接退出,并且将退出码返回给父进程,如果切换成功,就不会进行后面的代码,子进程执行完命令后就会自动退出。
5、判断内建命令
经过以上四个函数,我们已经完成了一个基础的自定义shell,但是bash提供的shell还有很多我们没有的功能,比如cd、echo等命令我们无法实现,它们有个共同的名字叫做内建命令,所以我们还要判断以下命令是否是内建命令的问题。
cd
为什么说我们的shell现在还无法实现cd的功能呢?因为我们目前实现命令都是通过子进程来实现的,如果输入的命令是cd,就变成子进程去执行cd命令,子进程改变了自己的工作路径,但这无法影响我们自己进程的工作路径,而且也没有意义。所以我们需要在执行命令之前判断一下当前命令是否是内建命令,如果是内建命令,就需要让我们的程序自己去执行。
判断是否是内建命令,我们现在只能用最笨的方法来判断,就是获取g_argv[0],通过比较判断它是不是内建命令。
如果判断成功,当前命令是cd命令,我们就要进入专门的函数来执行这个命令。cd这个命令一般有四种写法,分别是:1、只写cd,表示进入用户的家目录;2、cd -,表示进入上一个所在的工作目录;3、cd ~,也是表示进入用户的家目录;4、cd +路径,进入对应的工作路径下。接下来我们就来依次进行讲解
1、cd/cd ~
判断是否只写了cd很简单,我们只需要判断一下g_argv是否为1就行了。而判断是否是cd ~就需要再进行一次判断,判断g_argv[1]是否等于 ‘~’, 如果用户输入了cd或cd ~,我们就要进入用户的家目录,所以我们需要获取一下当前用户的家目录,用户的家目录路径也是在环境变量表中存储着的,所以我们需要利用getenv来获取一下当前用户的家目录路径。
const char* GETHOME()
{
return getenv("HOME");
}
其次切换工作路径我们需要使用到chdir函数,它需要的参数只有一个,就是将要切换的路径传给它就行,完整的切换进家目录的代码为
void CdHome()
{
std::string home = GETHOME();
chdir(home.c_str());
}
2、cd +路径
这种写法的cd命令比进入家目录还要简单,只需要判断g_argv[1]是否是 ‘~’ 或 ‘-’ ,都不是则代表为这种写法,切换路径的方式也是直接使用chdir进行切换。
3、cd –
这是实现最困难的一种写法,因为这种写法我们不仅要改变目前所处的路径,还要获取环境变量OLDPWD,PWD,并改变OLDPWD和PWD。
获取PWD的函数我们在之前已经写过了,而改变PWD值只需要在获取PWD这个函数的基础上进行修改就行了。首先,我们需要获取我们当前所处的路径,这回我们不再使用getenv这个函数,因为我们在改变工作路径时,并没有将环境变量表中的PWD一并改变,所以从环境变量表中只能获取到旧值,我们需要使用一个新的函数getcwd,这个函数可以将我们当前所处的工作路径格式化进我们传给它的一个数组中,并返回指向数组的指针。我们先创建一个数组cwd用于存储我们当前的路径,再创建一个数组cwdenv来存储环境变量PWD,然后我们再使用snprintf将“ PATH=%s”和cwd格式化进cwdenv中,就得到了新的环境变量,再使用putenv将新的PWD写入环境变量表中,putenv会先在环境变量表中查找是否有同名变量,有的话就进行替换,没有就创建一个新变量,这样我们的PWD就改成了我们当前所处的路径。
char cwd[1020];
char cwdenv[1024];
const char* GETPWD()
{
const char* pwd = getcwd(cwd, sizeof(cwd));
if(pwd != NULL)
{
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
putenv(cwdenv);
}
return getenv("PWD");
}
优化后的GETPWD函数就长这样。接下来对于获取和修改OLDPWD才是一件难事。
const char* GETOLDPWD(const char* old)
}
const char* oldpwd = getenv("OLDPWD");
putenv(oldpwdenv);
snprintf(oldpwdenv, sizeof(oldpwdenv), "OLDPWD=%s", old);
return oldpwd;
}
曾经我的想法是,仿照GETPWD函数的写法,先将当前的路径和OLDPWD记录下来,然后将它们两个互换,就可以简单的解决这个问题了。但是在我试验的时候,我发现,使用这种写法的时候,第一次输入cd -是可以正常运行的,但是从第二次开始就不行了,无论输入多少次它永远都会保持在同一个路径不变,在我询问ai后才知道,问题出现在putenv这个函数上,putenv这个函数并不是真正将改变后的环境变量覆盖再原变量上,而是将原变量指针指向的位置改为我们提供给他的指针位置,这就意味着一个问题:每次在我对OLDPWD和PWD进行交换时会出现一个问题,第一次交换时OLDPWD确实变成了PWD,交换是成功的,但是在第二次交换时,虽然我先oldpwd将旧值存储了下来,使用putenv将OLDPWD修改了,但是在我们重新格式化oldpwdenv时,也就是第三行代码,此时它修改的还是同一个地址里的值,而我们的oldpwd也是指向这个地址的,这下就完蛋了,oldpwd这个地址里的值变成了old,也就是曾经的PWD,相当于这次切换工作路径完全没变!换之前怎么样,换之后还是什么样。
所以为了解决这个问题,我就只能选择将一个函数劈成两半,第一半函数先获取OLDPWD里的值,回到cd函数内让进程先完成路径切换,并记录上个路径,第二个函数再接收这个路径,将OLDPWD修改。修改后的函数为:
char oldpwdenv[1024];
const char* GETOLDPWD()
{
const char* oldpwd = getenv("OLDPWD");
return oldpwd;
}
void CHOLDPWD(const char* old)
{
snprintf(oldpwdenv, sizeof(oldpwdenv), "OLDPWD=%s", old);
putenv(oldpwdenv);
}
if (ch == "-")
{
const char* oldpwd = getenv("PWD");
const char* pwd = GETOLDPWD();
chdir(pwd);
CHOLDPWD(oldpwd);
}
这样我们的cd -命令就能正常执行了,接下来附完整cd函数代码
char oldpwdenv[1024];
char cwd[1020];
char cwdenv[1024];
const char* GETPWD()
{
const char* pwd = getcwd(cwd, sizeof(cwd));
if(pwd != NULL)
{
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
putenv(cwdenv);
}
return getenv("PWD");
}
const char* GETOLDPWD()
{
const char* oldpwd = getenv("OLDPWD");
return oldpwd;
}
void CHOLDPWD(const char* old)
{
snprintf(oldpwdenv, sizeof(oldpwdenv), "OLDPWD=%s", old);
putenv(oldpwdenv);
}
const char* GETHOME()
{
return getenv("HOME");
}
void CdHome()
{
std::string home = GETHOME();
chdir(home.c_str());
}
void Cd()
{
if (g_argc == 1)
{
CdHome();
}
else
{
std::string ch = g_argv[1];
if (ch == "-")
{
const char* oldpwd = getenv("PWD");
const char* pwd = GETOLDPWD();
chdir(pwd);
CHOLDPWD(oldpwd);
}
else if(ch == "~")
{
CdHome();
}
else
{
std::string where = g_argv[1];
chdir(where.c_str());
}
}
}
echo
echo命令就比cd命令简单很多了,echo常见的三种写法是,echo +字符串,直接打印字符串,echo $?,打印上一个程序的退出码,echo $环境变量,打印某个环境变量名。我们来依次实现。
在我们识别到g_argv[0]为echo后,用一个string类型的变量opt接收g_argv[1],并进行比较,如果opt为“$?”,就打印退出码,首先我们要在原先的代码基础上加上退出码,先在全局定义一个整形变量exitcode,在执行命令函数里定义一个整形status,将其传给waitpid,等待成功后将exitcode赋值为status进行宏计算后的值。这样我们就获取了退出码。这样echo函数就能从全局拿到exitcode的值,直接将其打印出来并且将其置为0,表示此次进程的退出码为0。
如果opt不为“$?”,就判断opt[0]是否为‘$’如果是,就获取$后的字符串,并使用getenv寻找对应的环境变量,将返回的环境变量值打印。
如果以上两种情况都不满足,就将字符串直接打印出来。完整代码如下
void Echo()
{
if (g_argc == 2)
{
std::string opt = g_argv[1];
if (opt == "$?")
{
std::cout << exitcode << std::endl;
exitcode = 0;
}
else if(opt[0] == '$')
{
std::string envname = opt.substr(1);
const char* envvalue = getenv(envname.c_str());
if (envvalue)
std::cout << envvalue << std::endl;
}
else
{
std::cout << opt << std::endl;
}
}
}
6、判断是否为重定向
我们最后一个要实现的功能为重定向功能,这个函数要在分析命令之前进行。如何知道当前命令是否经过了重定向呢?我们从命令数组最后一个元素开始往前遍历,定义一个变量end表示最后一个数组下标,在遍历到头之前如果某一个元素是 ‘>’ 或 ‘<’,就代表当前命令经过了重定向。
接着我们还需要判断当找到的元素是 ‘>’ 时输入的到底是输出重定向还是输入重定向,所以我们还需要判断一下end的前一个位置是什么,如果也是‘>’,end的前一个位置置为0,若不是就将end位置置为0,并将命令截断,只保留前半部分命令用于执行。记录下重定向的类型为什么,无重定向记为0,‘<’记为1,‘>’记为2,‘>>’记为3 。
接下来我们需要知道重定向的目标文件,因为重定向符号和目标文件之间可能有多个空格,所以我们需要借用一个函数isspce,end位置为空格时判断为真,先将end++,因为我们之前将end位置置为0不为空格,然后将end传给isspace,若为真则end++,一直到为假为止,然后定义一个全局变量将文件名保存下来。
接着进入执行命令函数,在进程切换之前判断一下是否有重定向,并判断重定向的类型,以对应的方式打开文件,并使用dup2将文件进行重定向,这样我们就完成了整个重定向的操作。
void TrimSpace(char cmd[], int& end)
{
while (isspace(cmd[end]))
{
end++;
}
}
void RedirCheck(char cmd[])
{
redir = NONE_REDIR;
filename.clear();
int begin = 0;
int end = strlen(cmd) – 1;
while (end > begin)
{
// < >> >
if (cmd[end] == '<')
{
cmd[end++] = 0;
TrimSpace(cmd, end);
redir = INPUT_REDIR;
filename = cmd + end;
break;
}
else if(cmd[end] == '>')
{
if (end – 1 >= 0 && cmd[end – 1] == '>')
{
cmd[end – 1] = 0;
redir = APPEND_REDIR;
}
else
{
redir = OUTPUT_REDIR;
}
cmd[end++] = 0;
TrimSpace(cmd, end);
filename = cmd + end;
break;
}
else
{
end–;
}
}
}
int Execute()
{
pid_t id = fork();
if (id == 0)
{
if (redir == INPUT_REDIR)
{
int fd = open(filename.c_str(), O_RDONLY);
if (fd < 0) exit(2);
dup2(fd, 0);
}
else if (redir == OUTPUT_REDIR)
{
int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0) exit(2);
dup2(fd, 1);
}
else if(redir == APPEND_REDIR)
{
int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
if (fd < 0) exit(2);
dup2(fd, 1);
}
execvp(g_argv[0], g_argv);
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
exitcode = WEXITSTATUS(status);
}
return 0;
}
总结
至此,我们的简单的自定义shell就写完了,虽然它和我们平常用的shell还有不小的区别,但是以我们目前学习的知识只能写成这样了,编写这个程序的目的也不是要完全还原shell的所有功能,而是利用这么一种方式来让大家能够更好的掌握之前学习的知识,希望大家在看完这篇文章后能更好的掌握这一部分的知识。下附整个程序的完整代码:
#include <climits>
#include <cstdlib>
#include<ctype.h>
#include<iostream>
#include<cstdio>
#include <memory>
#include<pthread.h>
#include <sched.h>
#include<stdlib.h>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#define HOSTNAME "VM-0-17-ubuntu"
#define FORMAT "%s@%s:%s# "
#define COMMAND_SIZE 1024
#define MAXRACE 128
int exitcode = 0;
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
std::string filename;
int redir = NONE_REDIR;
int g_argc = 0;
char* g_argv[MAXRACE];
char cwd[1020];
char cwdenv[1024];
char oldpwdenv[1024];
const char* GETUSERNAME()
{
return getenv("USER");
}
const char* GETPWD()
{
const char* pwd = getcwd(cwd, sizeof(cwd));
if(pwd != NULL)
{
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
putenv(cwdenv);
}
return getenv("PWD");
}
const char* GETOLDPWD()
{
const char* oldpwd = getenv("OLDPWD");
return oldpwd;
}
void CHOLDPWD(const char* old)
{
snprintf(oldpwdenv, sizeof(oldpwdenv), "OLDPWD=%s", old);
putenv(oldpwdenv);
}
const char* GETHOME()
{
return getenv("HOME");
}
void MakeCommandLine(char cmd_command[], int size)
{
snprintf(cmd_command, size, FORMAT, GETUSERNAME(), HOSTNAME, GETPWD());
}
void PrintCommandLine()
{
char prompt[COMMAND_SIZE];
MakeCommandLine(prompt, sizeof(prompt));
printf("%s", prompt);
fflush(stdout);
}
bool GetCommandLine(char* out, int size)
{
char* com = fgets(out, size, stdin);
if (com == nullptr)
return false;
out[strlen(out) – 1] = 0;
if (strlen(out) == 0)
return false;
return true;
}
bool CommandTable(char* cmd_prompt)
{
#define SEP " "
g_argc = 0;
g_argv[g_argc++] = strtok(cmd_prompt, SEP);
while(g_argv[g_argc++] = strtok(nullptr, SEP));
if(g_argv[g_argc] == nullptr)
g_argc–;
return g_argc > 0 ? true : false;
}
void CdHome()
{
std::string home = GETHOME();
chdir(home.c_str());
}
void Cd()
{
if (g_argc == 1)
{
CdHome();
}
else
{
std::string ch = g_argv[1];
if (ch == "-")
{
const char* oldpwd = getenv("PWD");
const char* pwd = GETOLDPWD();
chdir(pwd);
CHOLDPWD(oldpwd);
}
else if(ch == "~")
{
CdHome();
}
else
{
std::string where = g_argv[1];
chdir(where.c_str());
}
}
}
void Echo()
{
if (g_argc == 2)
{
std::string opt = g_argv[1];
if (opt == "$?")
{
std::cout << exitcode << std::endl;
exitcode = 0;
}
else if(opt[0] == '$')
{
std::string envname = opt.substr(1);
const char* envvalue = getenv(envname.c_str());
if (envvalue)
std::cout << envvalue << std::endl;
}
else
{
std::cout << opt << std::endl;
}
}
}
bool IsInBuiltCommand()
{
std::string cmd = g_argv[0];
if (cmd == "cd")
{
Cd();
return true;
}
else if (cmd == "echo")
{
Echo();
return true;
}
return false;
}
void TrimSpace(char cmd[], int& end)
{
while (isspace(cmd[end]))
{
end++;
}
}
void RedirCheck(char cmd[])
{
redir = NONE_REDIR;
filename.clear();
int begin = 0;
int end = strlen(cmd) – 1;
while (end > begin)
{
// < >> >
if (cmd[end] == '<')
{
cmd[end++] = 0;
TrimSpace(cmd, end);
redir = INPUT_REDIR;
filename = cmd + end;
break;
}
else if(cmd[end] == '>')
{
if (end – 1 >= 0 && cmd[end – 1] == '>')
{
cmd[end – 1] = 0;
redir = APPEND_REDIR;
}
else
{
redir = OUTPUT_REDIR;
}
cmd[end++] = 0;
TrimSpace(cmd, end);
filename = cmd + end;
break;
}
else
{
end–;
}
}
}
int Execute()
{
pid_t id = fork();
if (id == 0)
{
if (redir == INPUT_REDIR)
{
int fd = open(filename.c_str(), O_RDONLY);
if (fd < 0) exit(2);
dup2(fd, 0);
}
else if (redir == OUTPUT_REDIR)
{
int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0) exit(2);
dup2(fd, 1);
}
else if(redir == APPEND_REDIR)
{
int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
if (fd < 0) exit(2);
dup2(fd, 1);
}
execvp(g_argv[0], g_argv);
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
exitcode = WEXITSTATUS(status);
}
return 0;
}
int main()
{
while(true)
{
//输出命令行提示符
PrintCommandLine();
//输入命令
char Command[COMMAND_SIZE];
if(!GetCommandLine(Command, COMMAND_SIZE))
{
continue;
}
RedirCheck(Command);
//分析命令
if(!CommandTable(Command))
continue;
//判断是否为内建命令
if(IsInBuiltCommand())
{
continue;
}
//执行命令
Execute();
}
return 0;
}



