跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

Linux 进程替换:exec 系列函数全解析与应用

Linux 进程替换技术利用 exec 系列函数在不创建新进程的情况下替换当前进程的用户态资源。文章详细解析 execlp、execvp 等六个函数的命名规则、参数差异及底层原理,通过虚拟地址空间变化解释为何成功无返回值。实战部分演示 fork+exec+wait 组合构建迷你 Shell,涵盖脚本执行、环境变量管理及错误处理方案,帮助开发者掌握 Linux 多任务执行核心逻辑。

Kubernet发布于 2026/2/10更新于 2026/7/2145 浏览
Linux 进程替换:exec 系列函数全解析与应用

一、先破误区:进程替换不是'创建新进程'

在讲具体函数前,必须先纠正一个常见误区:进程替换不会创建新进程。

我们回顾进程的本质:进程 = 内核数据结构(PCB/task_struct + 页表) + 用户态代码/数据。进程替换的核心是:

  • 保留内核数据结构(PID、进程组、打开的文件描述符等不变);
  • 彻底替换用户态资源(代码段、数据段、堆、栈被新程序覆盖);
  • 从新程序的'启动入口'(如 main 函数)开始执行,原进程的剩余代码不再执行。

举个通俗的例子:进程就像'演员',PID 是演员的'身份证',用户态代码 / 数据是'剧本'。进程替换相当于'演员不换(身份证不变),但换了一本全新的剧本,只演新剧本的内容'—— 不是换了个演员,而是同一个演员换了要演的内容。

代码演示:单进程执行 exec,验证 PID 不变

我们用一个简单例子,看 exec 替换后 PID 是否变化:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    printf("替换前:进程 PID = %d,即将执行 ls 命令\n", getpid());
    // 调用 execlp 替换为 ls 命令(ls 会列出当前目录文件)
    // 若替换成功,下面的 printf 不会执行(代码被覆盖)
    int ret = execlp("ls", "ls", "-l", NULL);
    if (ret == -1) {
        perror("execlp 失败");
        exit(1);
    }
    // 替换成功后,这行代码永远不会执行
    printf("替换后:这行不会打印\n");
    return 0;
}

编译运行(gcc exec_demo1.c -o exec_demo1 && ./exec_demo1),会看到类似输出:

替换前:进程 PID = 12345,即将执行 ls 命令
total 16
-rwxrwxr-x 1 ubuntu ubuntu 8960 10 月 1 10:00 exec_demo1
-rw-rw-r-- 1 ubuntu ubuntu 456 10 月 1 09:59 exec_demo1.c

然后用 echo $? 查看退出码(ls 执行成功退出码为 0),再用 ps -ef | grep 12345 查看 —— 会发现 PID 为 12345 的进程已经消失(ls 执行完就退出了),但替换过程中 PID 始终是 12345,没有新建进程。

二、exec 系列函数:6 个函数的'命名密码'与用法

Linux 提供了 6 个以 exec 开头的函数(统称 exec 函数簇),它们功能相似但参数格式不同。很多初学者会被这些函数的名字和参数搞混,其实只要掌握'命名规则',就能轻松区分 —— 每个函数名的后缀(l/v/p/e)都对应特定含义。

2.1 先记'命名密码':l、v、p、e 分别代表什么?

exec 函数簇的命名遵循严格规则,后缀字母对应参数格式或功能,我们先拆解这 4 个关键字母:

后缀字母英文全称核心含义
lList参数采用'列表形式'(可变参数,最后必须以 NULL 结尾,标记参数结束)
vVector参数采用'数组形式'(传入字符指针数组,最后一个元素必须是 NULL)
pPath自动从环境变量 PATH 中查找新程序的路径,无需手动写全路径(如 ls 而非 /bin/ls)
eEnvironment自定义环境变量(传入环境变量数组,覆盖进程默认继承的环境变量)

根据这 4 个字母的组合,6 个 exec 函数的关系如下(核心是 execve,其他 5 个都是它的封装):

  • 无 e:使用进程默认继承的环境变量(从父进程继承,如 PATH、HOME);
  • 有 e:必须手动传入环境变量数组,覆盖默认环境;
  • 无 p:必须写新程序的完整路径(如 /bin/ls);
  • 有 p:只需写程序名(如 ls),自动查 PATH。
2.2 6 个 exec 函数的原型与基础用法

我们按'常用程度'排序,逐一讲解每个函数的原型、参数含义和代码示例,重点关注 execlp、execvp(日常开发最常用)。

1. execlp:列表参数 + 自动查 PATH(最常用之一)

原型:

#include <unistd.h>
int execlp(const char* file, const char* arg, ...);
  • file:新程序的名称(如 ls、ps),会自动从 PATH 查找路径;
  • arg:命令行参数列表(第一个参数必须是程序名,最后以 NULL 结尾);
  • 返回值:只有失败返回 - 1(成功无返回,代码已覆盖)。

代码示例:用 execlp 执行 ls -l 命令

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    printf("执行 execlp(ls -l),PID = %d\n", getpid());
    // 参数说明:
    // 1. "ls":要执行的程序名(自动查 PATH 找到/bin/ls)
    // 2. "ls":第一个命令行参数(惯例是程序名)
    // 3. "-l":第二个命令行参数(ls 的选项)
    // 4. NULL:标记参数列表结束
    int ret = execlp("ls", "ls", "-l", NULL);
    if (ret == -1) {
        perror("execlp 执行 ls 失败");
        // 失败原因:如命令不存在、权限不足
        exit(1);
    }
    printf("替换成功后不会执行这行\n");
    return 0;
}

关键注意点:参数列表必须以 NULL 结尾,否则 exec 函数会读取到垃圾数据,导致执行失败。

2. execvp:数组参数 + 自动查 PATH(最常用之一)

当命令行参数较多时,用'列表形式'(execlp)会写很多参数,而'数组形式'(execvp)更简洁 —— 把参数存到数组里,直接传入函数。

原型:

#include <unistd.h>
int execvp(const char* file, char* const argv[]);
  • file:同 execlp,程序名(自动查 PATH);
  • argv:字符指针数组(每个元素是命令行参数,最后一个元素是 NULL);
  • 返回值:同 execlp,失败返回 - 1。

代码示例:用 execvp 执行 ps -ef 命令

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    // 命令行参数数组:ps -ef,最后一个元素必须是 NULL
    char* const argv[] = {
        "ps",
        "-ef",
        NULL
    };
    printf("执行 execvp(ps -ef),PID = %d\n", getpid());
    int ret = execvp("ps", argv); // 传入程序名和参数数组
    if (ret == -1) {
        perror("execvp 执行 ps 失败");
        exit(1);
    }
    printf("替换成功后不会执行这行\n");
    return 0;
}

适用场景:参数数量不确定(如从用户输入动态生成参数),用数组存储更灵活。

3. execl:列表参数 + 手动写全路径

execl 和 execlp 的唯一区别是:execl 必须写新程序的完整路径(无 p,不查 PATH),其他用法完全相同。

原型:

#include <unistd.h>
int execl(const char* path, const char* arg, ...);
  • path:新程序的完整路径(如 /bin/ls、/usr/bin/ps);
  • arg:同 execlp,参数列表以 NULL 结尾。

代码示例:用 execl 执行 /bin/ls -a

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    // 必须写全路径:/bin/ls,不能只写 ls
    int ret = execl("/bin/ls", "ls", "-a", NULL);
    if (ret == -1) {
        perror("execl 执行 ls 失败");
        // 若路径写错(如/bin/lss),会提示'没有那个文件或目录'
        exit(1);
    }
    return 0;
}

注意点:如果路径写错(如把 /bin/ls 写成 /bin/lss),execl 会失败,错误信息是'No such file or directory'。

4. execv:数组参数 + 手动写全路径

execv 和 execvp 的区别与 execl 和 execlp 一致:execv 必须写全路径,execvp 自动查 PATH。

原型:

#include <unistd.h>
int execv(const char* path, char* const argv[]);

代码示例:用 execv 执行 /usr/bin/ps -aux

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    char* const argv[] = {"ps", "-aux", NULL};
    // 写全路径:/usr/bin/ps
    int ret = execv("/usr/bin/ps", argv);
    if (ret == -1) {
        perror("execv 执行 ps 失败");
        exit(1);
    }
    return 0;
}
5. execle:列表参数 + 手动路径 + 自定义环境

execle 的后缀有 e,表示'自定义环境变量'—— 需要手动传入环境变量数组,覆盖进程默认继承的环境(如 PATH、HOME)。

原型:

#include <unistd.h>
int execle(const char* path, const char* arg, ..., char* const envp[]);
  • envp:环境变量数组(每个元素是'KEY=VALUE'格式,最后以 NULL 结尾);
  • 注意:参数列表必须以 NULL 结尾,然后再跟 envp(可变参数的特殊处理)。

代码示例:自定义环境变量,执行 echo $MY_ENV

我们先写一个简单的'打印环境变量'程序(echo_myenv.c),再用 execle 执行它并传入自定义环境:

  1. 编写 echo_myenv.c(打印 MY_ENV 环境变量):
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 获取环境变量 MY_ENV 的值
    char* my_env = getenv("MY_ENV");
    if (my_env) {
        printf("MY_ENV = %s\n", my_env);
    } else {
        printf("MY_ENV 未设置\n");
    }
    return 0;
}
  1. 编译 echo_myenv:gcc echo_myenv.c -o echo_myenv;
  2. 用 execle 执行 echo_myenv,传入自定义环境:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    // 自定义环境变量数组(KEY=VALUE 格式,最后 NULL 结尾)
    char* const envp[] = {
        "MY_ENV=hello_execle",
        "PATH=/bin:/usr/bin",
        NULL
    };
    // 执行 echo_myenv,传入自定义环境
    int ret = execle("./echo_myenv",
                     "echo_myenv",
                     NULL,
                     envp);
    if (ret == -1) {
        perror("execle 执行失败");
        exit(1);
    }
    return 0;
}

运行结果:

MY_ENV = hello_execle

关键注意点:自定义环境变量时,建议包含 PATH —— 否则新程序中若执行其他命令(如 ls),会因找不到路径而失败。

6. execvpe:数组参数 + 自动查 PATH + 自定义环境

execvpe 是'数组参数(v)+ 自动查 PATH(p)+ 自定义环境(e)'的组合,用法是 execvp 和 execle 的结合。

原型:

#include <unistd.h>
int execvpe(const char* file, char* const argv[], char* const envp[]);

代码示例:用 execvpe 执行 echo_myenv,自动查 PATH + 自定义环境

假设我们把 echo_myenv 放到 /usr/local/bin(该目录在 PATH 中),则无需写全路径:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    char* const argv[] = {"echo_myenv", NULL};
    char* const envp[] = {
        "MY_ENV=hello_execvpe",
        "PATH=/bin:/usr/bin:/usr/local/bin",
        NULL
    };
    // 只需写程序名"echo_myenv",自动查 PATH
    int ret = execvpe("echo_myenv", argv, envp);
    if (ret == -1) {
        perror("execvpe 执行失败");
        exit(1);
    }
    return 0;
}

运行结果:

MY_ENV = hello_execvpe
2.3 6 个 exec 函数的核心对比表

为了方便查阅,我们用表格总结 6 个函数的关键差异,帮你快速选择合适的函数:

函数名参数格式是否需全路径是否自定义环境核心场景
execl列表是否(默认继承)简单命令,参数少且固定
execlp列表否(查 PATH)否(默认继承)简单命令,不想写全路径(如 ls)
execle列表是是(覆盖)需自定义环境,参数少
execv数组是否(默认继承)参数多或动态生成,需全路径
execvp数组否(查 PATH)否(默认继承)参数多,不想写全路径(最常用)
execvpe数组否(查 PATH)是(覆盖)需自定义环境,参数多

选择口诀:参数少用 l,参数多用 v;不想写路径用 p,自定义环境用 e。

三、进程替换的底层原理:虚拟地址空间的'大换血'

我们结合之前讲的'虚拟地址空间'知识,拆解进程替换时内核和用户态的变化 —— 为什么代码被覆盖后,原进程的代码就不执行了?

3.1 虚拟地址空间的变化过程

进程替换的核心是'覆盖用户态地址空间的所有区域',具体过程如下(以 32 位系统为例):

  1. 读取新程序的 ELF 文件:exec 函数会先从磁盘读取新程序的 ELF 可执行文件(如 /bin/ls),解析其代码段、数据段的大小和位置。
  2. 释放原进程的用户态资源:内核释放原进程虚拟地址空间中'代码段、数据段、堆、栈'的映射关系,回收对应的物理内存(若其他进程不共享)。
  3. 分配新的虚拟地址区域:根据 ELF 文件的解析结果,在进程的虚拟地址空间中,为新程序的'代码段、数据段、堆、栈'划分对应的虚拟地址范围(如代码段从 0x08048000 开始)。
  4. 建立新的页表映射:内核为新程序的代码段、数据段分配物理内存页,建立'虚拟地址→物理地址'的页表映射(代码段标记为'只读 + 可执行',数据段标记为'可读 + 可写')。
  5. 设置程序计数器(PC):将 CPU 的程序计数器(PC)指向新程序的'入口地址'(ELF 文件中定义的 _start 函数,最终会调用 main)。

至此,进程的用户态资源已完全替换 —— 下一条执行的指令是新程序的入口,原进程的代码和数据再也不会被执行(已被释放或覆盖)。

3.2 为什么 exec 成功后没有返回值?

这是初学者最常问的问题 —— 答案就藏在上述过程中:

  • 若 exec 替换成功,原进程的代码段已被新程序覆盖,exec 函数本身的返回指令(原代码的一部分)也被删除了,根本无法返回任何值;
  • 只有当 exec 替换失败(如找不到程序、权限不足)时,原进程的代码段才没被覆盖,exec 函数才会返回 - 1,让我们能处理错误。

这也是为什么 exec 函数的错误处理很简单:'只要返回,就是失败',无需判断返回值是否为 0。

四、实战核心:fork + exec + wait 的组合(Shell 的核心逻辑)

单独使用 exec 函数意义不大(会覆盖当前进程的代码,比如父进程 exec 后就无法 wait 子进程了)。实际开发中,进程替换几乎都和 fork、wait 配合使用 ——父进程 fork 子进程,子进程 exec 替换为新程序,父进程 wait 回收子进程,这正是 Shell 执行命令的核心逻辑。

4.1 完整代码示例:模拟 Shell 执行 ls 命令

我们写一个简化版的'迷你 Shell',实现'输入 ls,执行 ls 命令'的逻辑:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 1024

int main() {
    char command[BUF_SIZE]; // 存储用户输入的命令
    while (1) {
        // 1. 打印命令提示符(模拟 Shell 的 [user@host dir]$)
        printf("[mini-shell]$ ");
        fflush(stdout); // 刷新缓冲区,确保提示符立即显示

        // 2. 获取用户输入的命令(如'ls -l')
        if (fgets(command, BUF_SIZE, stdin) == NULL) {
            perror("获取命令失败");
            continue;
        }

        // 处理换行符:fgets 会把回车符\n读入,需替换为\0
        command[strcspn(command, "\n")] = '\0';
        if (strlen(command) == 0) {
            continue; // 空命令,跳过
        }

        // 3. 拆分命令和参数(简化版:按空格拆分,仅支持单个空格分隔)
        char* argv[BUF_SIZE];
        int argc = 0;
        char* token = strtok(command, " "); // 第一次调用 strtok,传入命令字符串
        while (token != NULL) {
            argv[argc++] = token;
            token = strtok(NULL, " "); // 后续调用传入 NULL,继续拆分
        }
        argv[argc] = NULL; // 数组最后必须是 NULL

        // 4. fork 子进程,子进程 exec 替换,父进程 wait 回收
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork 失败");
            continue;
        }
        if (pid == 0) {
            // 子进程:exec 替换为新程序(用 execvp,自动查 PATH)
            execvp(argv[0], argv);
            // 只有 exec 失败才会执行到这里
            perror("命令执行失败");
            exit(1); // 子进程必须退出,否则会继续执行父进程的循环
        } else {
            // 父进程:wait 回收子进程,避免僵尸进程
            int status;
            waitpid(pid, &status, 0);
            // 可选:打印子进程退出码
            if (WIFEXITED(status)) {
                printf("命令执行完毕,退出码 = %d\n", WEXITSTATUS(status));
            }
        }
    }
    return 0;
}
4.2 运行效果与核心逻辑解析

编译运行(gcc mini_shell.c -o mini_shell && ./mini_shell),输入 ls -l,会看到类似输出:

[mini-shell]$ ls -l
total 48
-rwxrwxr-x 1 ubuntu ubuntu 8960 10 月 1 14:00 exec_demo1
-rw-rw-r-- 1 ubuntu ubuntu 456 10 月 1 09:59 exec_demo1.c
-rwxrwxr-x 1 ubuntu ubuntu 8896 10 月 1 13:30 echo_myenv
-rw-rw-r-- 1 ubuntu ubuntu 287 10 月 1 13:28 echo_myenv.c
-rwxrwxr-x 1 ubuntu ubuntu 9088 10 月 1 14:30 mini_shell
-rw-rw-r-- 1 ubuntu ubuntu 1568 10 月 1 14:29 mini_shell.c
命令执行完毕,退出码 = 0

核心逻辑拆解:

  1. 命令输入与解析:用 fgets 获取用户输入,strtok 按空格拆分命令和参数(如'ls -l'拆分为 argv = ["ls", "-l", NULL]);
  2. fork 子进程:父进程保留原代码(继续等待用户输入),子进程准备替换;
  3. 子进程 exec 替换:用 execvp 执行命令(自动查 PATH,无需写全路径),若失败则打印错误并退出;
  4. 父进程 wait 回收:等待子进程执行完毕,获取退出码,避免僵尸进程。

这正是 Linux 中 bash、zsh 等 Shell 的核心工作流程 —— 你每天输入的 ls、cd、gcc 等命令,背后都是这个'fork→exec→wait'的循环。

五、扩展知识点:实战中的常见问题与解决方案

5.1 问题 1:exec 执行脚本(Python/Shell)失败

exec 函数不仅能执行 C 语言编译的 ELF 程序,还能执行 Python、Shell 等脚本文件 —— 但需要注意脚本的'解释器声明'(第一行 #!/usr/bin/env python3 或 #!/bin/bash),否则 exec 会因'无法识别可执行格式'而失败。

代码示例:用 execvp 执行 Python 脚本

  1. 编写 Python 脚本(test.py):
#!/usr/bin/env python3 # 关键:声明解释器路径
print("Hello from Python script!")
print("Script PID:", __import__('os').getpid())
  1. 给脚本加执行权限:chmod +x test.py;
  2. 用 execvp 执行:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    char* const argv[] = {"./test.py", NULL};
    // 执行 Python 脚本(自动查 PATH,脚本有解释器声明)
    int ret = execvp("./test.py", argv);
    if (ret == -1) {
        perror("execvp 执行 Python 脚本失败");
        exit(1);
    }
    return 0;
}

运行结果:

Hello from Python script! Script PID: 12345 # 和原进程 PID 相同,验证未新建进程
5.2 问题 2:如何追加环境变量(而非覆盖)?

exec 函数中带 e 的函数(execle、execvpe)会覆盖默认环境变量,若想'追加'新环境变量(保留原有环境,新增变量),可按以下步骤操作:

  1. 用 extern char **environ 获取当前进程的环境变量数组(environ 是全局变量,存储所有环境变量);
  2. 新建一个环境变量数组,先复制 environ 的所有元素,再追加新变量;
  3. 将新数组传入 execle/execvpe。

代码示例:追加环境变量 MY_ENV

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main() {
    extern char** environ; // 获取当前环境变量数组
    int env_len = 0;
    // 1. 计算原有环境变量的数量
    while (environ[env_len] != NULL) {
        env_len++;
    }
    // 2. 新建环境变量数组(原有数量 + 1 个新变量 + 1 个 NULL)
    char* new_env[env_len + 2];
    int i;
    // 复制原有环境变量
    for (i = 0; i < env_len; i++) {
        new_env[i] = environ[i];
    }
    // 追加新环境变量
    new_env[i++] = "MY_ENV=append_env";
    new_env[i] = NULL; // 标记结束
    // 3. 执行 echo_myenv,传入追加后的环境
    int ret = execle("./echo_myenv", "echo_myenv", NULL, new_env);
    if (ret == -1) {
        perror("execle 失败");
        exit(1);
    }
    return 0;
}

运行结果:

MY_ENV = append_env # 新变量生效,原有环境变量(如 PATH)也保留
5.3 问题 3:exec 失败后子进程必须退出

如果子进程 exec 失败(如命令不存在),子进程会继续执行父进程的代码 —— 这会导致'子进程进入父进程的循环,也开始打印提示符、获取命令',出现'多个提示符重叠'的错误。

错误示例(子进程未 exit):

if (pid == 0) {
    execvp(argv[0], argv);
    perror("命令执行失败"); // 没有 exit,子进程会继续执行下面的循环
}

运行错误效果:

[mini-shell]$ lss # 输入不存在的命令
命令执行失败:No such file or directory
[mini-shell]$ [mini-shell]$ # 两个提示符重叠(父进程和子进程都打印)

解决方案:exec 失败后,子进程必须调用 exit 退出,终止执行流:

if (pid == 0) {
    execvp(argv[0], argv);
    perror("命令执行失败");
    exit(1); // 关键:子进程退出,避免执行父进程代码
}

六、总结与下一篇预告

本篇文章我们从'子进程如何执行新程序'切入,讲清了进程替换的本质(不新建进程,只换用户态资源),拆解了 exec 系列 6 个函数的命名规则、原型和用法,最后通过'迷你 Shell'实战,展示了 fork+exec+wait 的核心组合 —— 这是 Linux 中多任务执行的基石。核心要点可以总结为 3 句话:

  1. 进程替换的核心是'换代码数据,不换 PID',exec 成功无返回,失败返回 - 1;
  2. 6 个 exec 函数的区别在'参数格式(l/v)、路径查找(p)、环境变量(e)',日常开发优先用 execlp(简单命令)和 execvp(参数多);
  3. 实战中必须配合 fork 和 wait:父进程 fork 子进程,子进程 exec 替换,父进程 wait 回收,避免覆盖父进程代码或产生僵尸进程。

通过前四篇文章,我们已经掌握了进程控制的完整流程:创建(fork)→ 替换(exec)→ 等待(wait)→ 终止(exit)。但这些知识如何落地成一个能实际使用的工具?下一篇文章《实战 —— 微型 Shell 命令行解释器实现》,我们会基于前面的所有知识,写一个支持'内建命令(cd/export)、外部命令、环境变量管理'的完整 Shell,让你真正做到'学以致用'。

目录

  1. 一、先破误区:进程替换不是“创建新进程”
  2. 代码演示:单进程执行 exec,验证 PID 不变
  3. 二、exec 系列函数:6 个函数的“命名密码”与用法
  4. 2.1 先记“命名密码”:l、v、p、e 分别代表什么?
  5. 2.2 6 个 exec 函数的原型与基础用法
  6. 1. execlp:列表参数 + 自动查 PATH(最常用之一)
  7. 2. execvp:数组参数 + 自动查 PATH(最常用之一)
  8. 3. execl:列表参数 + 手动写全路径
  9. 4. execv:数组参数 + 手动写全路径
  10. 5. execle:列表参数 + 手动路径 + 自定义环境
  11. 6. execvpe:数组参数 + 自动查 PATH + 自定义环境
  12. 2.3 6 个 exec 函数的核心对比表
  13. 三、进程替换的底层原理:虚拟地址空间的“大换血”
  14. 3.1 虚拟地址空间的变化过程
  15. 3.2 为什么 exec 成功后没有返回值?
  16. 四、实战核心:fork + exec + wait 的组合(Shell 的核心逻辑)
  17. 4.1 完整代码示例:模拟 Shell 执行 ls 命令
  18. 4.2 运行效果与核心逻辑解析
  19. 五、扩展知识点:实战中的常见问题与解决方案
  20. 5.1 问题 1:exec 执行脚本(Python/Shell)失败
  21. 5.2 问题 2:如何追加环境变量(而非覆盖)?
  22. 5.3 问题 3:exec 失败后子进程必须退出
  23. 六、总结与下一篇预告
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于多 AI 模型并行的内容生成与对比分析工作流
  • 内网安全:隧道技术详解
  • 利用 QQ 私聊构建全自动化服务器运维助手
  • 基于 Hive、SpringBoot 和 Vue 的旅游数据分析与管理系统
  • 基于 SpringBoot+Vue 的考编论坛网站管理系统设计与实现
  • JavaScript 快速入门:条件语句与循环结构
  • 5 种小米智能家居接入 HomeAssistant 方案深度对比
  • AI 大模型技术原理、训练优化及行业应用全景解析
  • GitHub 汉化插件核心技术解析与实现
  • Python 行业现状与职业发展前景深度解析
  • LeetCode Hot 100 高频算法题刷题指南
  • TRAE 接入方舟 Coding Plan 使用教程
  • Spring Web 模块核心概念与 RESTful API 调用实战
  • 基于 Node-RED 与 Python 的边缘 AI 轻量级方案
  • 基于微信小程序的校园服务平台的设计与开发
  • Java 链表经典面试题实战:分割、回文、相交与环检测
  • 用 Python 实现会追着食物跑的 AI 贪吃蛇
  • Spring Boot + OpenAI 集成本地向量数据库 Chroma 实战
  • JavaScript 运算符详解:单目、双目与多目
  • 探索 Ovis 多模态大模型量化实战指南

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online