目录

Linux-自定义shell命令解释器

Linux 自定义shell命令解释器

本章的目的是:

1.模块化实现一个具备基本命令行解释功能的自定义bash。

2.通过实现自定义bash串讲先前的重要知识,尤其是环境变量和命令行参数的理解。

首先我们对大致的变量和核心功能做一个大概介绍。

一.功能概览

1. 命令行参数相关


#define MAXARGC 128
char *g_argv[MAXARGC];  // 存储解析后的命令行参数数组
int g_argc = 0;         // 参数个数计数器
  • 功能:存储用户输入命令解析后的各个参数
  • 示例:输入 ls -l /home 会被解析为 g_argv[0]="ls"g_argv[1]="-l"g_argv[2]="/home"

2. 环境变量相关


#define MAX_ENVS 100
char *g_env[MAX_ENVS];  // 环境变量存储数组
int g_envs = 0;         // 环境变量计数器
  • 功能:存储和管理 shell 的环境变量
  • 作用:为子进程提供执行环境

3.命令行参数表和环境变量表性质详解

特性环境变量表 (Environment Variables)命令行参数表 (Command-line Arguments)
级别进程级进程级
存储形式KEY=VALUE 字符串数组字符串指针数组
终止标记以 NULL 指针结尾以 NULL 指针结尾
继承性子进程继承父进程的环境变量子进程不继承父进程的参数,需要显式传递
修改性运行时可以动态修改通常在进程启动后只读

1.环境变量实际上是进程级的概念,但可以通过继承机制实现“系统级”的效果。

// 环境变量的继承链
系统启动 → init进程 → 登录shell → 当前shell → 子进程

2.环境变量表的内存布局


// 环境变量表在内存中的结构
char *environ[] = {
    "PATH=/usr/bin:/bin",
    "HOME=/home/user", 
    "USER=john",
    "SHELL=/bin/bash",
    NULL  // 结束标记
};

3.本次实现bash中,对于初始化环境变量表的操作


#define MAX_ENVS 100
char *g_env[MAX_ENVS];  // 自定义环境变量表
int g_envs = 0;         // 环境变量计数器

void InitEnv()
{
    extern char **environ;  // 系统全局环境变量表指针
    
    // 从父进程复制环境变量
    for(int i = 0; environ[i]; i++)
    {
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);  // 深拷贝
        g_envs++;
    }
    
    g_env[g_envs] = NULL;  // 必须的结束标记
    
    // 注册到系统
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;  // 重定向全局指针
}
  1. 深拷贝:避免直接使用父进程的环境变量指针
  2. 内存管理:需要手动管理 g_env 中字符串的内存
  3. 指针重定向:修改全局 environ 指向自定义表

对于之后要讲解的路径获取函数GetPwd,我们想实现实时更新系统环境变量中路径的值,就需要用到之前讲解的putenv进行修改,具体操作:


const char *GetPwd()
{
    const char *pwd = getcwd(cwd, sizeof(cwd));
    if(pwd != NULL)
    {
        // 更新 PWD 环境变量
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);  // 动态修改环境变量表
    }
    return pwd;
}

4.命令行参数表的特性:命令行参数表一般由一个参数计数器argc和参数表argv组成。前者记录表中的参数个数(包含命令和选项),后者则是存放具体的命令行参数。


// 命令行参数表的结构示例
// 用户输入: ls -l /home
char *argv[] = {
    "ls",      // 程序名/命令名
    "-l",      // 参数1
    "/home",   // 参数2  
    NULL       // 结束标记
};
int argc = 3;  // 参数个数

5.命令行参数表我们在进行命令行指令的解析和执行时会重点使用。我们使用strtok函数将命令行指令以字符串形式按空格分割,然后以循环方式依次解析指令。


#define MAXARGC 128
char *g_argv[MAXARGC];  // 命令行参数表
int g_argc = 0;         // 参数计数器

bool CommandParse(char *commandline)
{
    g_argc = 0;
    
    // 使用strtok解析命令行字符串
    g_argv[g_argc++] = strtok(commandline, " ");
    
    // 循环解析所有参数
    while((g_argv[g_argc] = strtok(nullptr, " ")) != nullptr)
    {
        g_argc++;
        if(g_argc >= MAXARGC - 1) break;  // 防止溢出
    }
    
    g_argv[g_argc] = NULL;  // 必须的结束标记
    return g_argc > 0;
}

6.在实现bash过程中,重点应用了环境变量的读取+设置+传递子进程,以及命令行参数的别名展开+命令行解析+命令传递。


4.别名映射表

在解析命令前,先在别名表中以key-value形式查看是否存在指令别名


std::unordered_map<std::string, std::string> alias_list;

5.辅助变量


char cwd[1024];     // 当前工作目录缓冲区
char cwdenv[1024];  // 环境变量格式的工作目录
int lastcode = 0;   // 上一个命令的退出状态码

6.核心功能模块

1. 环境信息获取函数

这些接口主要用于获取环境变量,并模仿系统bash中打印用户名-当前主机-当前工作目录的行为。


const char* GetUserName()    // 获取当前用户名
const char* GetHostName()    // 获取主机名  
const char* GetPwd()         // 获取当前工作目录
const char* GetHome()        // 获取家目录路径

2. 环境初始化模块


void InitEnv()
  • 功能

    • 从父进程继承环境变量
    • 复制到自定义环境变量数组
    • 添加测试环境变量 “HAHA=for_test”
    • 设置全局环境变量表

3. 内建命令实现

我们在这里展开什么叫内建命令。先来说什么不是内建命令:之后的代码中大家可以观察到,我们在除了内建命令以外的其他命令(如ls等等)都没有采用创建新的子进程+程序替换的方式执行指令:

https://i-blog.csdnimg.cn/direct/eff0e97995264014bb0c5ce77120d0b9.png

那么为什么所谓内建命令不采用这种方式呢?

我们可以试想:初始时我们有bash进程,此时输入了命令cd,功能大家都清楚——更改当前工作路径并回显。但是我们会发现实际上路径并不会改变,这时为什么?因为把cd这样的内建命令交给子进程做,都只会对子进程产生修改,而父进程bash的工作目录并不会改变。其他的一些内建命令也类似如此的理由设计为内建命令。

4. 命令行界面模块

提示符生成


void MakeCommandLine()    // 生成格式化的提示符
void PrintCommandPrompt() // 打印提示符
  • 格式[用户名@主机名 当前目录名]#
  • 示例[user@localhost ~]#

命令输入处理


bool GetCommandLine()  // 读取用户输入的命令行
bool CommandParse()    // 解析命令行为参数数组
void PrintArgv()       // 调试用:打印解析后的参数

5. 命令执行模块

内建命令检查

bool CheckAndExecBuiltin()
  • 功能:检查是否为内建命令,如果是则直接执行
  • 支持的内建命令:cdechoexportalias
  • 返回 true 表示已处理,无需创建子进程
外部命令执行

int Execute()
  • 流程

    1. fork() 创建子进程
    2. 子进程:execvp() 发生程序替换,执行外部命令
    3. 父进程:waitpid() 等待子进程结束
    4. 记录退出状态码到 lastcode

6. 辅助函数


std::string DirName(const char *pwd)  // 提取路径的最后一个目录名

主程序流程


int main()
  1. 初始化:调用 InitEnv() 设置环境变量

  2. 主循环

    • 打印提示符
    • 读取命令
    • 解析命令
    • 检查内建命令
    • 执行外部命令
  3. 循环继续直到用户退出

二.模块内部逻辑详细讲解

1.环境初始化模块(InitEnv)


void InitEnv()
{
    extern char **environ;  // 引用外部全局环境变量表
    memset(g_env, 0, sizeof(g_env));  // 清空自定义环境变量数组
    g_envs = 0;

    // 复制父进程的环境变量
    for(int i = 0; environ[i]; i++)
    {
        // 为每个环境变量字符串分配内存
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);  // 复制字符串内容
        g_envs++;
    }
    
    // 添加自定义测试环境变量
    g_env[g_envs++] = (char*)"HAHA=for_test";
    g_env[g_envs] = NULL;  // 环境变量表必须以NULL结尾

    // 将自定义环境变量设置到进程环境
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);  // 设置到系统环境变量
    }
    environ = g_env;  // 重定向全局环境变量指针
}

逻辑详解

  • 首先获取外部全局的 environ 变量(这是一个指向系统环境变量字符串数组的指针)。
  • 将 g_env 数组清零,并重置计数器 g_envs 为0。
  • 遍历 environ 数组,将每个环境变量字符串拷贝到 g_env 中(使用动态分配内存)。
  • 添加一个测试环境变量 “HAHA=for_test”,注意g_env的结尾用NULL来表示参数传递完毕。
  • 然后遍历 g_env 数组,使用 putenv 将每个环境变量设置到当前进程的环境中。
  • 最后将全局的 environ 指针指向我们的 g_env,这样后续的环境变量查找都会使用我们自定义的表

注意:这里有一个潜在问题,因为 putenv 的参数是字符串指针,而我们将动态分配的字符串指针传递给它,这些指针在程序运行期间一直有效(因为不会释放),所以没有问题。但是,如果后续要修改环境变量,需要小心处理。

2.内建命令模块

该模块是我们检测到位内建命令之后,不创建子进程而由bash直接执行的命令。

cd指令

在这里我们仅对默认的cd和具体的路径做了处理,对于特殊字符例如回到上一级目录,回到根目录并没有实现。


bool Cd()
{
    // cd argc = 1 表示只有"cd"命令,没有参数
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;  // 家目录为空则直接返回
        chdir(home.c_str());  // 切换到家目录
    }
    else  // 有参数的情况:cd <directory>
    {
        std::string where = g_argv[1];  // 获取目标目录参数
        
        // 特殊目录处理(目前未实现)
        if(where == "-")
        {
            // TODO: 应该切换到上一个工作目录
            // 需要维护一个previous_dir变量
        }
        else if(where == "~")
        {
            // TODO: 应该展开为用户家目录
        }
        else
        {
            chdir(where.c_str());  // 切换到指定目录
        }
    }
    return true;
}
  • 如果没有参数(g_argc==1,因为命令名是第一个参数,所以只有命令名时参数个数为1),则切换到家目录。

  • 如果有参数,检查第一个参数(g_argv[1]):

    • 如果是 -,表示切换到上一个目录(未实现)。
    • 如果是 ~,表示切换到家目录(未实现)。
    • 否则,切换到参数指定的目录。

注意chdir 系统调用成功返回0,失败返回-1,但这里没有处理错误

echo指令

我们这里对两个参数的情况(echo ***)做了处理。分别做了查看最近一个可执行程序的退出码,查看环境变量以及普通的printf逻辑,没有对重定向等操作定义和完善。


void Echo()
{
    if(g_argc == 2)  // 只处理单个参数的情况
    {
        std::string opt = g_argv[1];
        
        // 情况1: 输出上一条命令的退出码
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;  // 重置退出码
        }
        // 情况2: 输出环境变量值
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);  // 去掉$符号
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        // 情况3: 直接输出字符串
        else
        {
            std::cout << opt << std::endl;
        }
    }
    // 注意:当前实现不支持多个参数,如"echo hello world"
}

逻辑

  • 目前只处理一个参数的情况(即 echo 后面只有一个字符串)。
  • 如果参数是 $?,则打印上一个命令的退出码 lastcode,然后将其重置为0。
  • 如果参数以 $ 开头,则将其视为环境变量名,获取并打印该环境变量的值。
  • 否则,直接打印参数字符串。

局限:目前只能处理一个参数,例如 echo hello world 会被解析为两个参数,而该函数只处理第二个参数(即hello),忽略其余参数。

3.命令行解析模块(CommandParse)


bool CommandParse(char *commandline)
{
#define SEP " "  // 使用空格作为分隔符
    g_argc = 0;
    
    // 第一次调用strtok,获取第一个token(命令名)
    g_argv[g_argc++] = strtok(commandline, SEP);
    
    // 循环获取后续所有参数,直到返回NULL
    while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
    
    g_argc--;  // 修正计数器(因为最后一次循环会多计数)
    
    return g_argc > 0 ? true:false;  // 至少有一个token才返回true
}

在这里,我们定义了分隔符为SEP(这里是空格)进行命令行参数的分割。

逻辑

  • 使用 strtok 函数以空格为分隔符将命令行字符串分割成多个令牌。
  • 第一个令牌通过 strtok(commandline, SEP) 获取,后续令牌通过 strtok(nullptr, SEP) 获取。
  • 每个令牌的指针被存入 g_argv 数组,并增加 g_argc 计数。
  • 当 strtok 返回 NULL 时停止,此时 g_argc 多计了一次,所以减一。
  • 最后返回是否解析到至少一个参数(即命令名)。

示例:

  • 输入:"ls -l /home/user"

  • 处理过程:

    1. strtok(commandline, " ") → "ls" → g_argv[0]
    2. strtok(nullptr, " ") → "-l" → g_argv[1]
    3. strtok(nullptr, " ") → "/home/user" → g_argv[2]
    4. strtok(nullptr, " ") → NULL → 循环结束
  • 结果:g_argc = 3g_argv = ["ls", "-l", "/home/user", NULL]

注意strtok 会修改原始字符串,将分隔符替换为 \0,因此原始命令字符串会被破坏。

4.内建命令检查模块(CheckAndExecBuiltin)


bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];  // 获取命令名
    
    // 命令分发逻辑
    if(cmd == "cd")
    {
        Cd();        // 内建命令,直接在当前进程执行
        return true; // 返回true表示已处理,无需fork
    }
    else if(cmd == "echo")
    {
        Echo();      // 内建命令
        return true;
    }
    else if(cmd == "export")
    {
        // TODO: 设置环境变量功能
        return true;
    }
    else if(cmd == "alias")
    {
        // TODO: 别名设置功能
        // 如:alias ll='ls -l'
        return true;
    }

    return false;  // 返回false表示不是内建命令,需要外部执行
}

逻辑

  • 将第一个参数(命令名)转换为字符串。
  • 与已知内建命令比较,如果匹配则调用相应的函数,并返回 true 表示已处理。
  • 如果不是内建命令,返回 false,以便后续执行外部命令。

5.外部命令执行模块 (Execute)

在经过命令行解析之后,我们需要对解析之后的指令进行执行(这里按逻辑已经确认是外部命令)。


int Execute()
{
    // 步骤1: 创建子进程
    pid_t id = fork();
    
    if(id == 0)  // 子进程分支
    {
        // 在子进程中执行外部命令
        execvp(g_argv[0], g_argv);  // 执行命令,替换当前进程映像
        
        // 如果execvp失败,执行到这里
        exit(1);  // 以错误码退出子进程
    }
    
    // 父进程分支
    int status = 0;
    // 等待子进程结束
    pid_t rid = waitpid(id, &status, 0);
    
    if(rid > 0)  // 成功等待到子进程结束
    {
        // 提取子进程的退出状态码
        lastcode = WEXITSTATUS(status);
    }
    
    return 0;
}
  • 使用 fork 创建子进程。
  • 在子进程中,调用 execvp 执行命令,如果执行失败则退出子进程(退出码1)。
  • 在父进程中,使用 waitpid 等待子进程结束,并获取退出状态。
  • 使用 WEXITSTATUS 宏从状态中提取退出码,并记录到 lastcode 中。

6.主循环(main)

为了不读取一个指令就退出bash程序,我们把bash设计为一个死循环,以便循环读取指令并执行。


int main()
{
    InitEnv();  // 一次性环境初始化

    while(true)  // 无限命令循环
    {
        // 阶段1: 显示提示符
        PrintCommandPrompt();  // 显示[user@host dir]# 
        
        // 阶段2: 读取命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline)))
            continue;  // 读取失败或空命令,重新循环
        
        // 阶段3: 解析命令
        if(!CommandParse(commandline))
            continue;  // 解析失败,重新循环
        
        // 阶段4: 检查内建命令
        if(CheckAndExecBuiltin())
            continue;  // 如果是内建命令且已执行,跳过外部执行
        
        // 阶段5: 执行外部命令
        Execute();  // 创建子进程执行
    }
    
    return 0;
}

逻辑

  • 初始化环境变量。

  • 进入无限循环:

    1. 打印命令提示符。
    2. 读取用户输入的命令行。
    3. 如果读取失败(如EOF)则跳过本次循环。
    4. 解析命令行,如果解析失败(空命令)则跳过。
    5. 检查是否为内建命令,如果是则执行并跳过后续步骤(外部命令执行)。
    6. 如果不是内建命令,则创建子进程执行外部命令。

7.路径显示处理(DirName)

我们发现以上的打印结果会把完整的当前路径打出,而系统的bash是只取当前的工作目录名。所以我们需要把路径进行处理。


std::string DirName(const char *pwd)
{
    std::string dir = pwd;
    
    // 特殊情况:根目录
    if(dir == "/") return "/";
    
    // 查找最后一个斜杠位置
    auto pos = dir.rfind("/");
    
    if(pos == std::string::npos) 
        return "BUG?";  // 理论上不应该出现
    
    // 返回最后一个斜杠后的部分
    return dir.substr(pos+1);
}

逻辑

  • 如果当前目录是根目录 “/",则返回 “/"。
  • 否则,查找最后一个 “/” 的位置,返回该位置之后的子字符串(即最后一个目录名)。
  • 如果找不到 “/",返回 “BUG?"。

示例

  • /home/user → rfind("/")找到位置5 → substr(6) → "user"
  • /usr/local/bin → 找到位置10 → substr(11) → "bin"

8.提示符生成 (MakeCommandLine)


void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}

逻辑

  • 使用 snprintf 格式化提示符字符串,格式为 [用户名@主机名 当前目录名]#
  • 其中当前目录名只显示最后一级目录。

三.完整源码与效果展示

源码如下:


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <unordered_map>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

// 下面是shell定义的全局数据

// 1. 命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0; 

// 2. 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;

// 3. 别名映射表
std::unordered_map<std::string, std::string> alias_list;

// for test
char cwd[1024];
char cwdenv[1024];

// last exit code
int lastcode = 0;

const char *GetUserName()
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()
{
    //const char *pwd = getenv("PWD");
    const char *pwd = getcwd(cwd, sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

void InitEnv()
{
    extern char **environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0;

    //本来要从配置文件来
    //1. 获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        // 1.1 申请空间
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    g_env[g_envs++] = (char*)"HAHA=for_test"; //for_test
    g_env[g_envs] = NULL;

    //2. 导成环境变量
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}

//command
bool Cd()
{
    // cd argc = 1
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];
        // cd - / cd ~
        if(where == "-")
        {
            // Todu
        }
        else if(where == "~")
        {
            // Todu
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

void Echo()
{
    if(g_argc == 2)
    {
        // echo "hello world"
        // echo $?
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    }
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);
}

void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out, int size)
{
    // ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out, size, stdin);
    if(c == NULL) return false;
    out[strlen(out)-1] = 0; // 清理\n
    if(strlen(out) == 0) return false;
    return true;
}

// 3. 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc = 0;
    // 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline, SEP);
    while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
    g_argc--;
    return g_argc > 0 ? true:false;
}

void PrintArgv()
{
    for(int i = 0; g_argv[i]; i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc: %d\n", g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    else if(cmd == "export")
    {
    }
    else if(cmd == "alias")
    {
       // std::string nickname = g_argv[1];
       // alias_list.insert(k, v);
    }

    return false;
}

int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    int status = 0;
    // father
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);
    }
    return 0;
}

int main()
{
    // shell 启动的时候,从系统中获取环境变量
    // 我们的环境变量信息应该从父shell统一来
    InitEnv();

    while(true)
    {
        // 1. 输出命令行提示符
        PrintCommandPrompt();

        // 2. 获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline)))
            continue;

        // 3. 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();

        // 检测别名
        // 4. 检测并处理内键命令
        if(CheckAndExecBuiltin())
            continue;

        // 5. 执行命令
        Execute();
    }
    //cleanup();
    return 0;
}

效果如下:


wujiahao@VM-12-14-ubuntu:~/process_test$ ./myshell
[wujiahao@None process_test]# echo hello
hello
[wujiahao@None process_test]# ls
Makefile  myshell  myshell.cc  other.cc  proc.c