why not me


目录

1. 进程替换

2. 自定义shell编写

完整代码


1. 进程替换

进程替换没有创建新进程,而是把当前进程的代码和数据用新程序的代码和数据覆盖式替换,一旦替换成功,就执行新代码

进程替换使用exec*系列函数,只有失败有返回值(-1),成功时没有返回值

后缀全称含义
llist参数以可变参数列表传递,以NULL结尾
vvector参数以指针数组传递,以NULL结尾
ppath自动在 $PATH 环境变量中搜索可执行文件
eenvironmen允许显式传入自定义的环境变量数组

详解与用法

1. execl — 列表传参 + 指定完整路径
int execl(const char *path, const char *arg0, ..., (char *)NULL);
用法:已知程序的绝对/相对路径,参数个数固定且较少
示例:execl("/bin/ls", "ls", "-l", "/tmp", NULL);

2. execv — 数组传参 + 指定完整路径
int execv(const char *path, char *const argv[]);
示例:char *argv[] = {"grep", "-rn", "TODO", "./src", NULL};
      execv("/usr/bin/grep", argv);

3. execle — 列表传参 + 指定路径 + 自定义环境
int execle(const char *path, const char *arg0, ..., (char *)NULL, char *const envp[]);
用法:需要为新程序提供完全不同的环境变量(而非继承父进程),同时参数用列表传递。
示例:char *envp[] = {"HOME=/sandbox", "LANG=C", NULL};
     execle("/usr/bin/python3", "python3", "script.py", NULL, envp);

4. execve — 数组传参 + 指定路径 + 自定义环境 ⭐
int execve(const char *pathname, char *const argv[], char *const envp[]);
示例:char *argv[] = {"nginx", "-g", "daemon off;", NULL};
     char *envp[] = {"NGINX_CONF=/etc/nginx/custom.conf", NULL};
     execve("/usr/sbin/nginx", argv, envp);

5. execlp — 列表传参 + PATH 搜索
int execlp(const char *file, const char *arg0, ..., (char *)NULL);
用法:不知道程序完整路径,依赖 $PATH 查找,参数固定。
示例:// 无需写 /bin/echo,自动在 PATH 中查找
     execlp("echo", "echo", "Hello World", NULL);

6. execvp — 数组传参 + PATH 搜索
int execvp(const char *file, char *const argv[]);
最实用的方法
示例:// 模拟 shell 执行用户输入的命令
     char *argv[] = {"find", ".", "-name", "*.c", NULL};
     execvp("find", argv);

7. execvpe — 数组传参 + PATH 搜索 + 自定义环境 (GNU/Linux 扩展)
int execvpe(const char *file, char *const argv[], char *const envp[]);
示例:char *argv[] = {"node", "app.js", NULL};
     char *envp[] = {"NODE_ENV=production", "PORT=8080", NULL};
     execvpe("node", argv, envp);

使用参考

需要 exec?
├── 参数个数编译期已知? ──→ 是 → 用 l 系列
│   ├── 需要自定义环境? ──→ execle
│   ├── 需要 PATH 搜索? ──→ execlp
│   └── 都不需要?       ──→ execl
│
└── 参数个数运行时确定? ──→ 用 v 系列
    ├── 需要自定义环境 + PATH搜索? → execvpe (Linux)
    ├── 需要自定义环境?           → execve
    ├── 需要 PATH 搜索?           → execvp
    └── 都不需要?                 → execv

fork和exec是黄金组合,几乎永远不要单独调用exec!!!

标准模式

pid_t pid = fork();
if (pid == 0) {
    // 子进程:exec 替换自己
    execvp("some_cmd", argv);
    perror("exec failed");  // 只有失败才会执行到这里
    _exit(127);             // 必须用 _exit,不能用 exit
}
// 父进程:waitpid(pid, ...)

2. 自定义shell编写

我们要写一个shell实现类似的功能,比如查看文件信息,创建一个目录,回到上级目录等等,首先要区分外部程序和内置命令,外部程序是一个独立可执行文件,通常存储在usr/bin目录下,如mkdir,ls等,shell遇见外部程序通常会fork子进程然后exec进程替换父进程waitpid,有系统调用。内置命令通常shell进程自己去执行。

对于外部程序,我们可以这样做


pid_t id=fork();
if(id==0)
{
    execvp(g_argv[0],g_argv);
    exit(1);
}
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0)
lastcode=WEXITSTATUS(status);//获取进程退出码
return 0;

这里需要自己实现一个字符串数组,实现类似于环境变量表的功能,用g_argv存储

strtok是c语言用于字符串分割的函数,这是为了处理像ls -l -a这种有空格的情况要单独把每个字符存储起来

char *strtok(char *str, const char *delim);
str 待分割的字符串。首次调用传入字符串指针,后续调用必须传 NULL
delim 分隔符字符串
返回值 指向下一个字符串的指针,没有时返回空

使用如下

#define SEP " "
        //ls -l -a
        g_argc=0;
        g_argv[g_argc++]=strtok(CommandLine,SEP);
        while(g_argv[g_argc++]=strtok(nullptr,SEP));
        g_argc--;

对于内置命令要单独进行判断,执行代码逻辑

这里以cd命令举例:

bool Cd()
{
      if(g_argc==1)
{
    std::string home=GetHome();
    if(home.empty()) return true;
    chdir(home.c_str());
}
else
{
    std::string where=g_argv[1];
    if(where=="-")
    {}         
    else if(where=="~")
    {}
    else
    {
        chdir(where.c_str());
    }
}
    return true;
}
完整代码
#include <fcntl.h>
#include <ctype.h>
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<unistd.h>
#include<stdlib.h>
#include <sys/wait.h>
#define format "[%s@%s %s]$ "
#define Command_size 1024
#define MaxArgv 128
#define MaxEnvs 200
//命令行参数表
char* g_argv[MaxArgv];
int g_argc;

//退出码
int lastcode=0;

//for-testMaxEnvs
char cwd[1024];
char cwdenv[1024];

//环境变量表
char* g_env[MaxEnvs];
int g_envs;

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

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

const char* GetPwd()
{
        //const char* pwd=getenv("PWD");
        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?"NONE":home;
}

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

void Echo()
{
 std::string where=g_argv[1];
                if(where=="$?")
                {
                std::cout<<lastcode<<std::endl;
                lastcode=0;
                }
                if(where[0]=='$')
                {
                std::string env_name=where.substr(1);
                const char* env_value=getenv(env_name.c_str());
                if(env_value!=NULL)
                std::cout<<env_value<<std::endl;
                }
}

std::string CommandSimple(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* prompt,int size)
{
        snprintf(prompt,size,format,GetUserName(),GetHostName(),CommandSimple(GetPwd()).c_str());
}

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

bool CommandEnter(char* CommandLine,int size)
{
        char* c=fgets(CommandLine,size,stdin);
        if(c==NULL) return false;
        CommandLine[strlen(CommandLine)-1]=0;
        if(strlen(CommandLine)==0) return false;
        return true;
}

bool CommandPrase(char* CommandLine)
{
#define SEP " "
        //ls -l -a
        g_argc=0;
        g_argv[g_argc++]=strtok(CommandLine,SEP);
        while(g_argv[g_argc++]=strtok(nullptr,SEP));
        g_argc--;
        if(g_argc>0)
        return true;
        else
        return false;
}

bool CheckCommandBuildin()
{
        std::string head=g_argv[0];
        if(head=="cd")
        {
                Cd();
                return true;
        }
        if(head=="echo")
        {
                Echo();
                return true;
        }


        return false;
}

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

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=Test";
        g_env[g_envs]=NULL;
        for(int i=0;g_env[i];i++)
        putenv(g_env[i]);
        environ=g_env;

}

int main()
{
        InitEnv();
        while(true)
        {
                //输出命令行提示符
                PrintCommandLine();
                char CommandLine[Command_size];

                //获取用户输入的命令
                if(!CommandEnter(CommandLine,sizeof(CommandLine)))
                continue;

                //命令行分析
                if(!CommandPrase(CommandLine))
                continue;

                //检测并处理内键命令
                if(CheckCommandBuildin())
                continue;

                //执行命令
                Excute();

        }

        return 0;
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐