【 linux 】进程替换与自定义shell编写
·

why not me
目录
1. 进程替换
进程替换没有创建新进程,而是把当前进程的代码和数据用新程序的代码和数据覆盖式替换,一旦替换成功,就执行新代码
进程替换使用exec*系列函数,只有失败有返回值(-1),成功时没有返回值
| 后缀 | 全称 | 含义 |
| l | list | 参数以可变参数列表传递,以NULL结尾 |
| v | vector | 参数以指针数组传递,以NULL结尾 |
| p | path | 自动在 $PATH 环境变量中搜索可执行文件 |
| e | environmen | 允许显式传入自定义的环境变量数组 |
详解与用法
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;
}
更多推荐




所有评论(0)