【Linux】进程控制(二)----进程程序替换、编写自主Shell命令行解释器(简易版)

目录

前言

书接上文【Linux】进程控制(一)----进程创建、进程终止、进程等待详情请点击,今天继续介绍【Linux】进程控制(二)----进程程序替换、编写自主Shell命令行解释器(简易版)

一、进程程序替换

  • fork() 之后,父子各自执行父进程代码的一部分,但是如果子进程想执行一个全新的程序(子进程有自己的代码、自己的数据)呢?进程的程序替换来完成这个功能!
  • 程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中

替换函数

execl函数

cpp 复制代码
int execl(const char *path, const char *arg, ...);
  • const char *path: 程序也是文件,通过路径+文件名查找,该参数是进程执行的代码
  • const char *arg, ...:可变参数,const char *arg:我们需要知悉程序的程序名;...:给程序传递的命令行的选项(可不使用),NULL结尾

可变参数理解

  • double sum(int count, ...) :必须要有一个实际的参数
cpp 复制代码
#include <stdio.h>
#include <stdarg.h>  // 可变参数相关头文件

// 函数:累加任意个数 double sum(int count, ...)  {
    double total = 0.0;
    va_list args;           // 1. 声明可变参数列表
    
    va_start(args, count);  // 2. 初始化可变参数列表
    
    // 3. 遍历并累加所有参数
    for (int i = 0; i < count; i++) {
        double num = va_arg(args, double);  // 获取下一个double参数
        total += num;
    }
    
    va_end(args);           // 4. 清理可变参数列表
    
    return total; }

int main()  {
    double result1 = sum(3, 1.1, 2.2, 3.3);
    printf("sum_double(3, 1.1, 2.2, 3.3) = %.2f\n", result1);
    
    double result2 = sum(5, 0.5, 1.5, 2.5, 3.5, 4.5);
    printf("sum_double(5, 0.5, 1.5, 2.5, 3.5, 4.5) = %.2f\n", result2);
    
    double result3 = sum(1, 99.9);
    printf("sum_double(1, 99.9) = %.2f\n", result3);
    
    double result4 = sum(0);
    printf("sum_double(0) = %.2f\n", result4);
    
    double result5 = sum(4, 1, 2.5, 3, 4.7);
    printf("sum_double(4, 1, 2.5, 3, 4.7) = %.2f\n", result5);
    
    return 0; } 
  • execl程序替换演示

1. 单个进程程序替换

  • 我们从结果可以看到,在没有使用execl程序替换时,进程运行printf打印进程pid,再调用execl进程程序替换,执行"/usr/bin/ls"路径下的ls -a -l -n程序
  • 因为程序替换了,进程已经执行另一个程序代码了,所以后续我们自己写的代码没有了 ,不会再执行我们自己写的代码
  • 如果程序替换失败返回-1,进程成功不会也不需要有返回值


  • 因为程序替换我们不需要判断是否替换失败,直接在exec系列函数后面打印程序替换失败即可,因为程序替换成功之后后续代码根本不会执行,只要往后执行了后续代码,则程序替换失败

2. 子进程程序替换

  • execl("/usr/bin/ls", "ls", "-a", "-l", "-n", NULL);中,"/usr/bin/ls":表示进程需要执行哪个文件,后面的"ls", "-a", "-l", "-n",:表示执行方法,这个代码中的ls并不冲突

execv函数

  • int execv(const char *path, char *const argv[]);:和execl第一个参数都是传入文件路径,只不过execv函数后面使将执行方法放到数组中(以NULL结尾)

1. execv函数演示

2. execv函数实现加载器作用

  • 通过main函数的argv数组参数,我们获得命令行参数,将参数传入到execv函数中,加载不同指令执行,实现将不同程序加载进内存中,形成进程执行

实现在命令行输入参数,将根据参数实现相关进程

execlp/execvp函数

execlp函数

  • int execlp(const char *file, const char *arg, ...);:execlp函数第一个参数只需要传入文件名称即可,不需要文件路径
  • 执行指定命令,需要让execlp在环境变量PATH中寻找指定程序


    execvp函数
  • int execvp(const char *file, char *const argv[]);:对比execlp,只是传入参数保存到数组中

execle/execvpe函数

  • 这两个函数分别比上面的execl和execvp函数多了一个参数char * const envp[]:环境变量
cpp 复制代码
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 上面我们子进程进程替换的都是系统的命令,我们可以替换我们自己的程序来执行吗?必然是可以的

  • 再创建一个.cc文件

  • execl函数,传入当前目录下mycmd程序的路径,mycmd执行方式


    execvpe函数演示

  • 修改mycmd.cc,打印argv数组内容和env数组内容(命令行参数和环境变量)

  • 修改myexec.c文件

  • 所以我们main函数的默认参数int main(int argc, char* argv[], char* env[])是通过程序替换由对应进程通过exec**e的方式将命令行参数表和环境变量传递给目标程序的
  • 除了能将我们自定义的环境变量传入,也可以传入系统的环境变量
  • 上面传入的环境变量要不传入系统的,要不就是我们自己的,那我们要在系统的环境变量中添加几个自定义环境变量,再传入给目标程序,如何做呢?


execve函数(系统调用)

  • execve是系统调用函数,上面的exec*函数(库函数)底层都是调用的execve函数

命名理解

  • l(list) :表示参数采用列表
  • v(vector) :参数用数组
  • p(path) :有 p 自动搜索环境变量 PATH
  • e(env) :表示自己维护环境变量

替换原理

  • 进程替换,是将子进程从父进程继承的数据和代码替换成自己的数据和代码,程序替换并没有创建新的进程
  • 但是子进程和父进程不是代码共享,数据写时拷贝吗?子进程替换了代码,父进程的代码不是也会替换吗?
  1. 这里可以理解成代码如果被替换,代码也要进行写时拷贝
  2. 在我们shell命令行执行ls -a -l命令时,其实就是bash fork()创建子进程,exec*子进程程序替换,父进程waitpid等待子进程完成任务
  3. 我们的程序要变为进程,是先创建PCB数据结构,再加载代码和数据的,但是我们的程序是如何加载到内存中的呢?我们的exec就类似于一种"加载器",让我们进程执行exec后的代码

二、自主Shell命令行解释器

  • 创建三个文件,main.cc负责上层调用,myshell.cc命令行相关实现代码,myshell.h函数声明

myshell命令行提示符实现

  • shell是一个进程,实现shell,首先我们得实现出自己的命令行提示符(字符串)来,在提示符后面等待用户输入命令
  • 命令行提示符包括[]、用户名(gy)、主机名(@VM-16-12-centos)和当前所在的工作路径,再包括一个$符号
cpp 复制代码
#include "myshell.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
static std::string GetUserName()
{}
static std::string GetHostName()
{}
static std::string GetPwd()                                                                      
{}                                                                                   
void PrintCommandPrompt()                                                              
{                                                                                      
     std::string user = GetUserName();                                                  
     std::string hostname = GetHostName();                                              
     std::string pwd = GetPwd();                 
     printf("[%s@%s %s]# ", user.c_str(), hostname.c_str(), pwd.c_str());
} 
  • 在myshell.cc文件中,对于GetUserName这些函数,我们一般只允许在该文件内部调用,上层main.cc文件中,我们只需要调用PrintCommandPrompt函数来实现命令行参数提示符,并不需要将GetUserName函数接口暴露给上层调用,所以使用static修饰函数
  • 获取环境变量,使用getenv函数,获取成功返回指向该环境变量字符串的指针;失败返回NULL

cpp 复制代码
static std::string GetUserName()
{
	 std::string username = getenv("USER");
   if(username.empty())
       return "None";
   return username;    
}
static std::string GetHostName()
{
	 std::string hostname = getenv("HOSTNAME");
   if(hostname.empty())
       return "None";
   return hostname;   
}
static std::string GetPwd()                                                                      
{
	 std::string pwd= getenv("PWD");
   if(pwd.empty())
       return "None";
   return pwd;  
}        
  • main函数中调用PrintCommandPrompt()函数,编译运行代码
cpp 复制代码
 #include "myshell.h"
int main()
{
     //1. 输出命令行提示符
     PrintCommandPrompt();                                                                        
     return 0;
}
  • 我们发现确实打印出来了自己的命令行提示符,但是后面又接着打印了系统的提示符
  • 因为main函数调用PrintCommandPrompt()函数后该进程直接就结束了
  • 命令行解释器是一个死循环 的软件
cpp 复制代码
#include "myshell.h"
int main()
{
     while(true)
     {
     		//1. 输出命令行提示符
     		PrintCommandPrompt();  
     }                                                         
     return 0;
}

获取用户命令

  • 上面代码实现了打印命令行提示符,但是在一直打印提示符,我们要实现打印命令行提示符后,等待用户输入命令
  • 获取键盘用户输入的命令,首先对于命令(ls -a -l -n),操作系统内部是将其看作一个整体字符串,而不是ls一个字符串,-a一个字符串...因此我们使用fgets函数直接获得整个长字符串,将从键盘(stdin)获得的字符串整体写入到char数组中
cpp 复制代码
//myshell.cc
//获取用户的键盘输入  
bool GetCommandString(char cmd_str_buff[], int len)  
{  
    if(cmd_str_buff == NULL || len <= 0)  
         return false;  
    char* ret = fgets(cmd_str_buff, len, stdin);  
    if(ret == NULL)  
        return false;  
    return true;                                                                                   
}    
//main.cc
#define SIZE 1024
int main()
{
     char commandstr[SIZE];
     while(true)
     { 
       //1. 输出命令行提示符
       PrintCommandPrompt();
       //2. 获取用户输入命令
       GetCommandString(commandstr, SIZE);                                                          
       printf("echo %s", commandstr);
     }
     return 0;
 }
  • 运行代码,我们发现,我们的shell输出命令行提示符之后就停在这里,不再是反复打印了,停在这里就是等用户键盘输入命令行
  • 输入命令行之后,打印出来了我们从键盘获得的命令,成功获取
  • 但是有一个问题,我们打印的时候明明没有换行符(\n),但是打印还是换行了,这是为什么呢?因为我们在输入ls -a -l -n之后,我们在结尾还输入了enter回车键,所以我们获取用户输入时最后一个\n我们需要将其替换成\0
  • 那么我们fgets函数有可能获得字符串为0吗?即cmd_str_buff长度为0?答案是不可能,我们在使用fgets获取键盘字符串时,即使没有输入任何字符串,但是我们最后一定会敲enter回车键,因此不可能为0,所以strlen(cmd_str_buff) - 1不会越界
cpp 复制代码
//myshell.cc
//获取用户的键盘输入  
bool GetCommandString(char cmd_str_buff[], int len)  
{  
    if(cmd_str_buff == NULL || len <= 0)  
         return false;  
    char* ret = fgets(cmd_str_buff, len, stdin);  
    if(ret == NULL)  
        return false; 
    // ls -a -l\n -> ls -a -l\0                                                                                 
    cmd_str_buff[strlen(cmd_str_buff) - 1] = 0;
    return strlen(cmd_str_buff) == 0 ? false : true;                                                                                  
}    

对命令行字符串进行解析(切割)

  1. 实现命令,我们需要将我们上面获得的字符串进行解析,解析为命令行参数表,因为命令行参数表我们是需要继承给子进程的,所以我们定义成全局的char数组
cpp 复制代码
//myshell.h
#ifndef __MYSHELL_H__
#define __MYSHELL_H__

#include <stdio.h>

#define ARGS 64
                                                                                                                                  
void PrintCommandPrompt();                           
bool GetCommandString(char cmd_str_buff[], int len); 
bool ParseCommandString(char cmd[]);
#endif   
 
//myshell.cc
char* gargv[ARGS] = {NULL};
int gargc = 0;        
  • 将字符串"ls -a -l"解析为"ls" 、"-a" 、"-l",并将单个字符串放入命令行参数表中。我们可以将字符串直接的空格替换成\0,依次获取单个字符串,遇到\0获得一个字符串,再从\0处往后,再次遇到\0获得下一个字符串,这里我们可以使用strtok函数来实现字符串截取
  • char *strtok(char *str, const char *delim):strtok返回一个指向字符串的指针,如果没有符合的子串,或者已经截取完成就会返回NULL(0)。传入的第一个参数是字符串数组,第二个参数是分割方式,这里我们是空格分割
cpp 复制代码
#include <stdio.h>  
#include <string.h>  

int main()  
{  
    char buffer[] = "ls -a -l -c -d";                                                              
    char* substr = strtok(buffer, " ");        
    printf("%s\n", substr);  
    char* substr1 = strtok(NULL, " ");        
    printf("%s\n", substr1);                   
    return 0;                                               
}  
  • 如果需要往后获取后面的字符串,传入的第一个参数为NULL
  • 首先,当出入的数组为空,则直接返回false,解析失败;如果不为空,根据空格将字符串分割
  • while((bool)(gargv[gargc++] = strtok(NULL, SEP))):将后面的字符串分割,并保存在gargv数组中,当解析完最后一个子串,再往后解析时,返回NULL,保存在数组中,gargc++(比如ls -a -l解析完成之后保存在数组中为"ls" 、"-a" 、"-l"、NULL,gargc为4,因为NULL返回NULL的时候也++了,因此gargc还需要--)
  • 同时使用条件编译来查看分割结果
cpp 复制代码
//myshell.cc
  // 解析命令  
  bool ParseCommandString(char cmd[])  
 {           
      if(cmd == NULL)  
         return false;  
 #define SEP " "  
     // "ls -a -l" -> "ls" "-a" "-l"  
     gargv[gargc++] = strtok(cmd, SEP);  
     while((bool)(gargv[gargc++] = strtok(NULL, SEP)));  
     gargc--;  
 #define DEBUG                                      
 #ifdef DEBUG                                       
     printf("gargc: %d\n", gargc);
     printf("----------------------------------------\n");                                          
     for(int i = 0; i < gargc; i++)
     {                                           
         printf("gargv[%d] : %s\n", i, gargv[i]);
     }                                                    
     printf("----------------------------------------\n");
     for(int i = 0; gargv[i]; i++)
     {                                           
         printf("gargv[%d] : %s\n", i, gargv[i]);         
     }                                
 #endif 
     return true;                                                                                   
 } 
  • 上面我们输入命令ls -a -l ,结果没问题,但是当我们再输入pwd,我们看到gargc = 4,gargv中保存的命令参数也是不对的。通过分析我们可以知道,当我们上次输入命令后再次输入命令,参数数组没有清空,导致这次参数数组中还保留着上次的gargc 和gargv
  • 因此我们在最开始都得初始化
  • 同时当我们在第2步操作获取用户输入命令时,如果没有获取到用户输入,那么就不需要进行第三步的命令行解析
cpp 复制代码
//myshell.h
void InitGlobal();
//myshell.cc
void InitGlobal()                                                       
 {                                                                       
     gargc = 0;                                                          
     memset(gargv, 0, sizeof(gargv));                                                               
 } 
 //main.cc
 #define SIZE 1024
int main()
{
     char commandstr[SIZE];
     while(true)
     { 
     	//0.初始化
     	InitGlobal();
       //1. 输出命令行提示符
       PrintCommandPrompt();
       //2. 获取用户输入命令
       if(!GetCommandString(commandstr, SIZE))
           continue;                                                          
       //3.对命令行字符串进行解析
       ParseCommandString(commandstr);
     }
     return 0;
 }
 
  • 从调试信息可以看到,代码解析已经没有问题,现在我们注释掉调试信息

实现命令

  • 执行命令,我们不能够直接程序替换,调用exec*系列接口实现,因为一旦当前程序直接调用,那么后续执行完程序替换程序之后,该程序就结束了,不会再继续运行myshell的死循环进程
  • 因此我们要创建子进程,让子进程来完成程序替换
  • 我们的参数列表数组保存着解析完成的命令,没有包含命令路径,所以我们在选择exec*函数时要选择带p的;我们保存在数组,所以我们得选择带v(vector)的,所以我们可以选择execvp/execvpe,环境变量传入还是不传入没有关系,这里我选择execvp函数完成

实现普通命令

cpp 复制代码
//执行命令                                              
 void ForAndExec()            
 {                            
     pid_t id =fork();        
     if(id < 0)               
     {                        
         perror("fork");      
         return;              
     }                                                              
     else if(id == 0)                                               
     {                                                              
         //子进程                                                   
         execvp(gargv[0], gargv);
         exit(0);
     }
     else
     {
         //父进程:等待
         pid_t rid = waitpid(id, nullptr, 0);                                                     
     }                                                                                    
 }    

实现内建命令

  • 上面我们已经实现了大部分命令,但是cd ...命令没有生效。我们之前进行路径切换,本质是父进程bash进程路径切换,fork后,路径会被子进程继承下去,因此子进程pwd后能查到新路径。
  • 但是我们代码子进程cd到某个路径之后,直接退出了,不会影响父进程,因此父进程路径不变
  • 因此,像cd命令这样只能由父进程shell自己执行,不能交给子进程执行,我们把这样只能由shell自己执行的命令叫做内建命令
  • 我们实现内建命令就得在实现普通命令之前判断是否为内建命令,如果是就shell自己执行,不是则交给子进程执行
  • 常见内建命令:cd 、echo
cd命令
  • cd命令,我们更改当前的工作路径,使用系统调用chdir函数
  • 当gargc == 2,说明cd后面跟了目标路径,直接chdir(gargv[1])即可,跟的是-或~时特殊处理(~:进入家目录;-:表示进入上一次目录);当gargc == 1,说明cd后面没有跟目标路径,直接回到家目录
  • 处理~和-,当为 ~时,直接进入家目录;当为-时,我们需要使用getenv函数获取oldpwd,pwd和oldpwd不会自动根据进程而自动实时更新,都是需要我们自己来更新的 ,所以我们一般不使用getenv来获得当前工作路径参数,而是使用系统调用来getcwd来直接获取当前进程所处路径
cpp 复制代码
//myshell.h
//使用系统调用,获取当前进程正所处的工作路径
static std::string GetPwd()                                                                                                 
{
    char buf[1024];
    getcwd(buf, sizeof(buf));
    return buf;
}
//通过getenv获取oldpwd
static std::string GetOldPwd()
{
     std::string oldpwd = getenv("OLDPWD");
     return oldpwd.empty() ? "None" : oldpwd;
} 
//进入家目录
static std::string GetHomePath()
{
    std::string home = getenv("HOME");
    return home.empty() ? "/" : home;                                                            
}

bool BuiltInCommandExec()
{
     //gargv[0]
     std::string cmd = gargv[0];
     bool ret = false;
     if(cmd == "cd")
     {
         std::string currentPwd = GetPwd();
         if(gargc == 2)
         {
             std::string target = gargv[1];
             if(target == "~")
             {
                 setenv("OLDPWD", currentPwd.c_str(), 1);
                 ret = true;
                 chdir(GetHomePath().c_str());                                                                                      
             }
             else if(target == "-")
              {
                 std::string OldPwd = GetOldPwd();
                 setenv("OLDPWD", currentPwd.c_str(), 1);
                 ret = true;
                 chdir(OldPwd.c_str());
             }
             else
             {
                  setenv("OLDPWD", currentPwd.c_str(), 1);
                  ret = true;
                  chdir(gargv[1]);
             }
         }
         else if(gargc == 1)
         {
             setenv("OLDPWD", currentPwd.c_str(), 1);
             ret = true;
             chdir(GetHomePath().c_str());
         }
         else{}
         }
     }
     return ret;
 }   
 //main.cc
 int main()
 {
     char commandstr[SIZE];
     while(true)
     {
       //0. 初始化
       InitGlobal();
       //1. 输出命令行提示符
       PrintCommandPrompt();
       //2. 获取用户输入命令
       if(!GetCommandString(commandstr, SIZE))
           continue;
       //3. "ls -a -l"->ls -a -l
       //对命令行字符串进行解析->命令行参数表
       ParseCommandString(commandstr);
       //4.检测命令,内建命令让shell自己执行
      if(BuiltInCommandExec())                                                                      
      {
         continue;
      }
       // 4. 执行命令,让子进程执行
       ForAndExec();
     }
     return 0;
 }
更新环境变量表
  • 上面我们使用的系统调用getcwd来获得当前进程的所处的路径,我们如何实时更新环境变量表中的当前路径呢?
  • 我们的进程继承了父进程的环境变量表,首先我们创建一个全局变量,代表当前shell的新的环境变量表(环境变量表必须全局有效)

snprintf:将某个数据按照一定的格式到str中,snprintf(pwd, sizeof(pwd), "PWD=%s", buf):按照PWD= 的字符写入到pwd中

cpp 复制代码
static std::string GetPwd()                                                                                                 
{
    char buf[1024];
    getcwd(buf, sizeof(buf));
    snprintf(pwd, sizeof(pwd), "PWD=%s", buf);
    putenv(pwd);
    return buf;
}   
命令行提示符修改
  • 系统的shell命令行路径只显示当前目录shell,我们自己实现的shell显示出全部路径
cpp 复制代码
static std::string GetPwd()                                                                                                 
{
    char buf[1024];
    getcwd(buf, sizeof(buf));
    snprintf(pwd, sizeof(pwd), "PWD=%s", buf);
    putenv(pwd);
    
    std::string pwd_label = buf;
    const std::string pathsep = "/"; 
    auto pos = pwd_label.rfind(pathsep);
    if(pos == std::string::npos)
    {
        return "None";
    }
    pwd_label = pwd_label.substr(pos + pathsep.size());
    return pwd_label.empty() ? "/" : pwd_label; 
}   
  • 这样处理之后,我们cd -时获取当前路径就只有最后的目录了,因此我们再创建一个GetCwd函数来获取完整的当前工作路径
cpp 复制代码
static std::string GetCwd()                                                                                                 
{
    char buf[1024];
    getcwd(buf, sizeof(buf));
    return buf;
}
echo命令
echo $?和echo打印字符
  • 在系统shell中,我们输入一个命令,使用echo $?能够查到最近一个子进程的退出码,现在我们myshell也来实现这个功能
  • 父进程waitpid等待时,关注返回信息,定义全局变量lastcode获取最近子进程退出码
cpp 复制代码
int lastcode = 0; //获取最近子进程退出码     
void ForAndExec()            
 {                            
     pid_t id =fork();        
     if(id < 0)               
     {                        
         perror("fork");      
         return;              
     }                                                              
     else if(id == 0)                                               
     {                                                              
         //子进程                                                   
         execvp(gargv[0], gargv);
         exit(0);
     }
     else
     {
         //父进程:等待
         int status = 0;
         pid_t rid = waitpid(id, &status, 0);    
         if(rid > 0)
         {
         		lastcode = WEXITSTATUS(status);
         }                                                 
     }                                                                                    
 }    
  • echo命令也是内建命令,当echo后面的是$?说明是查看退出码的指令,将lastcode打印出来,因为echo也是一个进程,因此打印之后将lastcode置为0;如果后面不是$?,直接将echo后面的内容打印在显示器上
cpp 复制代码
bool BuiltInCommandExec()
{
     //gargv[0]
     std::string cmd = gargv[0];
     bool ret = false;
     if(cmd == "cd")
     {
         std::string currentPwd = GetPwd();
         if(gargc == 2)
         {
             std::string target = gargv[1];
             if(target == "~")
             {
                 setenv("OLDPWD", currentPwd.c_str(), 1);
                 ret = true;
                 chdir(GetHomePath().c_str());                                                                                      
             }
             else if(target == "-")
              {
                 std::string OldPwd = GetOldPwd();
                 setenv("OLDPWD", currentPwd.c_str(), 1);
                 ret = true;
                 chdir(OldPwd.c_str());
             }
             else
             {
                  setenv("OLDPWD", currentPwd.c_str(), 1);
                  ret = true;
                  chdir(gargv[1]);
             }
         }
         else if(gargc == 1)
         {
             setenv("OLDPWD", currentPwd.c_str(), 1);
             ret = true;
             chdir(GetHomePath().c_str());
         }
     }
     else if(cmd == "echo")
     {                    
          if(gargc == 2)
          {                                         
              std::string args = gargv[1];
              if(args == "$?")           
              {
                  printf("lastcode: %d\n", lastcode);
                  lastcode = 0;
                  ret = true;                                                                             
               }        
               else     
               {        
                  printf("%s\n", args.c_str());
                  ret = true; 
               }        
          }                          
       }         
     return ret;
 }   
echo打印环境变量
cpp 复制代码
bool BuiltInCommandExec()
{
     //gargv[0]
     std::string cmd = gargv[0];
     bool ret = false;
     if(cmd == "cd")
     {
         std::string currentPwd = GetPwd();
         if(gargc == 2)
         {
             std::string target = gargv[1];
             if(target == "~")
             {
                 setenv("OLDPWD", currentPwd.c_str(), 1);
                 ret = true;
                 chdir(GetHomePath().c_str());                                                                                      
             }
             else if(target == "-")
              {
                 std::string OldPwd = GetOldPwd();
                 setenv("OLDPWD", currentPwd.c_str(), 1);
                 ret = true;
                 chdir(OldPwd.c_str());
             }
             else
             {
                  setenv("OLDPWD", currentPwd.c_str(), 1);
                  ret = true;
                  chdir(gargv[1]);
             }
         }
         else if(gargc == 1)
         {
             setenv("OLDPWD", currentPwd.c_str(), 1);
             ret = true;
             chdir(GetHomePath().c_str());
         }
     }
     else if(cmd == "echo")
     {                    
          if(gargc == 2)
          {                                         
              std::string args = gargv[1];
              if(args == "$")           
              {
              		if(args[1] == '?')
              		{
              		     printf("lastcode: %d\n", lastcode);
                      lastcode = 0;
                      ret = true;   
              		}
                  else              
                  {
                      const char* name = &args[1];
                      printf("%s\n", getenv(name));
                      lastcode = 0;
                      ret = true;                                                                                                  
                   }                                                                                 
               }        
               else     
               {        
                  printf("%s\n", args.c_str());
                  ret = true; 
               }        
          }                          
       }         
     return ret;
 }         

完整自主shell代码

详情请点击查看

相关推荐
数研小生2 小时前
Full Analysis of Taobao Item Detail API taobao.item.get
java·服务器·前端
2301_765703142 小时前
开发一个简单的Python计算器
jvm·数据库·python
QCzblack2 小时前
第三周作业
数据库
Root_Hacker2 小时前
sql注入学习笔记
数据库·sql·web安全·网络安全·oracle·网络攻击模型
H Journey2 小时前
Linux 下添加用户相关
linux·运维·服务器·添加用户
IT邦德2 小时前
基于OEL8环境的图形化部署Oracle26ai
数据库·oracle
一心赚狗粮的宇叔2 小时前
mongosDb 安装及Mongosshell常见命令
数据库·mongodb·oracle·nosql·web·全栈
零基础的修炼2 小时前
Linux网络---网络层
运维·服务器·网络