一、全局配置与状态(代码最开头)
变量 类型 作用 basesize = 1024const int 命令行缓冲区和 pwd 缓冲区大小 argvnum = 64const int gargv最多存 64 个参数envnum = 64const int 自维护环境表 genv最多 64 项gargv[64]static char*\[\] 解析后的参数表,末尾必须 NULL(exec 族要求) gargcstatic int 参数个数 lastcodestatic int 上一条命令退出码, echo $?就读它genv[64]static char*\[\] shell 自己维护的环境变量表(后面重点讲) pwd[1024]/pwdenv[1024]static char\[\] 当前目录 / 拼好的 "PWD=xxx"字符串几个设计要点:
- 这些都是全局 static,生命周期等于整个程序,后续函数随便用。
gargv是char*数组,指向命令行缓冲区内部的各个单词(strtok 原地切出来的),不是拷贝。lastcode是整个 shell 的状态核心------echo $?的 $? 不是系统变量,就是打印这个变量。
二、提示符函数组(6 个函数,作用一条链)
GetUserName → GetHostName → GetPwd → LastDir → MakeCommandLine → PrintCommandLineGetUserName / GetHostName:
getenv("USER")、getenv("HOSTNAME"),从进程环境表读用户名和主机名。找不到返回
"None"。注意
getenv返回的指针是只读的(指向 environ 表内部),不要尝试修改。GetPwd(含一个关键动作):
cppif (getcwd(pwd, sizeof(pwd)) == NULL) return "None"; snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd); putenv(pwdenv);
getcwd是系统调用:问内核"我的当前工作目录在哪",结果写入pwd。snprintf拼出"PWD=/home/xx"存进全局pwdenv。putenv(pwdenv)把这个字符串写回进程的真实环境表(注意:写的是pwd里的,不是genv,这就是后面的坑之一,详见第八节)。
维度 getcwdgetenv头文件 <unistd.h><stdlib.h>获取对象 当前工作目录(绝对路径) 环境变量的值 结果存哪 你自己的缓冲区(或动态分配) 进程环境块里的内部静态区 谁管生命周期 调用者 进程环境(后续调用可能覆盖) 失败表现 返回 NULL + errno(ERANGE 缓冲区太小 / EACCES 无权限 / ENOENT 目录被删)返回 NULL,无 errno 区分 "不存在" 对应的修改函数 chdir()setenv()/putenv()LastDir:
取最后一个
/后面的部分做提示符目录显示(/home/xx→xx)。MakeCommandLine:拼成
[user@host dir]#。PrintCommandLine:
printf("%s", MakeCommandLine().c_str()); fflush(stdout); // 必须 fflush,因为提示符没 \n这是难点:
printf默认是行缓冲(有\n才真正输出),提示符没有换行,所以必须手动fflush,否则你输入命令时提示符还憋在缓冲区里没显示出来。
三、GetCommandLine:读一行
cppif (fgets(buf, size, stdin) == NULL) { printf("\n"); return false; } buf[strlen(buf) - 1] = '\0'; // 去掉 fgets 读进来的 \n if (strlen(buf) == 0) return false;
fgets读到换行或缓冲区满为止,换行会保留在 buf 里,所以要手动把最后一位(\n)换成'\0'。- 三个返回值语义:
- 正常一行 →
true- 空行(直接回车,去 \n 后为空)→
false,主循环 continue 重新打印提示符- EOF(Ctrl+D)→ 打印换行、
false------这里埋了个 bug,见第八节。
四、ParseCommandLine:strtok 拆词(难点①)
const char *sep = " "; gargv[gargc++] = strtok(buf, sep); // 第一次调用传 buf while ((gargv[gargc++] = strtok(NULL, sep)) != NULL) ; gargc--;strtok 的工作原理(这是最值得深挖的地方):
- 第一次调用
strtok(buf, " "):从 buf 开头找第一个非空格字符,记下单词起点;再从那里往后找空格,找到就把空格替换成'\0',返回单词起点指针。同时,strtok 内部用一个静态指针记住当前位置(停在刚才那个'\0'之后)。- 后续调用
strtok(NULL, " "):不传新字符串,strtok 从内部记住的位置继续切下一个单词。- 连续空格自动跳过------所以
ls -l和ls -l结果一样。- 它直接修改原字符串:
buf里的空格被改写成'\0',于是buf被原地拆成好几段,gargv[i]只是指向这些段的首地址。举例:
输入
ls -l /home,解析后内存里是ls\0-l\0/home\0,gargv = {"ls", "-l", "/home", NULL}。最后那个
gargc--是因为 while 循环在赋 NULL 之后又把 gargc 加了一次(循环条件先赋值后判断),所以要多退一格,但 NULL 已经写进数组------这正是 exec 族需要的结尾标记。潜在崩溃点:
如果输入是纯空格(如
" "),第一个strtok返回 NULL,gargv[0]=NULL,后面CheckAndExecBuiltCommand里strcmp(gargv[0], "exit")会对 NULL 做 strcmp → 段错误。
五、环境变量:InitEnv / AddEnv(难点②:三套环境表)
这个程序里其实有三套环境,初学者最容易混淆:
表 是谁 怎么产生的 谁在用 environ内核 + libc 维护的进程真实环境表 ( extern char **environ,以 NULL 结尾)进程启动时由 exec 传入 getenv/putenv操作它genvshell 自己 malloc 拷贝的一份 InitEnv从 environ 拷贝,export追加传给 execvpe给子进程pwdenv全局静态数组 GetPwd每次拼PWD=xxx并putenv只写回 environ,不进 genvInitEnv:
cppextern char **environ; // 声明:进程真实环境表 for (; environ[i] && i < envnum - 1; i++) { genv[i] = (char *)malloc(strlen(environ[i]) + 1); strcpy(genv[i], environ[i]); } genv[i] = NULL;为什么要 malloc 拷贝而不是直接
genv[i] = environ[i]?因为
putenv、setenv可能会移动/改写 environ 表项,拷一份自己的副本才安全。这个 shell 后续要给子进程一份"自己说了算"的环境(用户export加进去的变量也要在),所以它维护自己的genv。AddEnv:
cppwhile (genv[i]) i++; // 找到末尾 genv[i] = malloc(strlen(item)+1); strcpy(genv[i], item); genv[++i] = NULL;注意它没有检查 genv 是否已满 64 项------
export超过 64 次会数组越界写(内存破坏)。
六、ExecuteCommand:fork / exec / waitpid(难点③,全程序核心)
cpppid_t id = fork(); if (id < 0) { perror("fork"); lastcode = 1; return; } if (id == 0) { execvpe(gargv[0], gargv, genv); // 子进程 perror(gargv[0]); // exec 失败才会走到这 exit(127); } int status = 0; waitpid(id, &status, 0); // 父进程 if (WIFEXITED(status)) lastcode = WEXITSTATUS(status); else if (WIFSIGNALED(status)) lastcode = 128 + WTERMSIG(status); else lastcode = 100;① fork():一次调用,两次返回
- fork 会复制当前进程(写时拷贝 COW:不修改数据时父子共享物理内存页,谁先改谁复制),创建子进程。
- 返回值语义:父进程得到子进程的 PID(>0);子进程得到 0;失败得 -1。
- 判断
id == 0就是在问"我现在是不是子进程?"------这就是 fork 后父子代码分道扬镳的经典写法。② execvpe:exec 家族里功能最全的一个
execvpe(文件名, argv, envp)拆开看:
exec:用新程序替换当前进程映像------代码段、数据段、堆栈全换,PID 不变。v:参数以char*[](argv 数组)传入。p:自动按 PATH 环境变量搜索可执行文件,所以不用写/bin/ls,写ls就行。e:用自定义环境genv作为新程序的环境表(不是默认的 environ)。关键:
exec 成功不返回------新程序跑起来后,"子进程"这个身份就变成
ls进程了,perror和exit(127)那两行永远不会执行;只有 exec 失败(比如命令不存在)才返回 -1,打印错误并
exit(127)。127 是 shell 约定:"命令找不到"。
③ waitpid:父进程收尸
waitpid(id, &status, 0)阻塞等待指定子进程结束,回收其资源(僵尸进程回收)。status是一个 int,退出信息被打包在特定位里,必须用宏拆开:
WIFEXITED(status):是否正常退出(调用了 exit / main 返回)→WEXITSTATUS取低 8 位退出码。WIFSIGNALED(status):是否被信号杀死(如kill、Ctrl+C 的 SIGINT)→128 + WTERMSIG(status),这是 shell 惯例:被 9 号信号杀就是 137。- 拆出的结果存进全局
lastcode,供echo $?读取。
七、内建命令 CheckAndExecBuiltCommand
逐个看:
exit:
直接
exit(0)终止整个进程。为什么 shell 的 exit 必须是内建?
因为如果是外部程序,
exit只能结束那个子进程,shell 自己还活着。cd:
cppif (gargc == 2) { if (chdir(gargv[1]) != 0) perror("cd"); } else { chdir(getenv("HOME") ? getenv("HOME") : "/"); } lastcode = 0;
chdir是系统调用,只改变当前进程(shell)自己的工作目录------这也是必须内建的原因:外部子进程改了目录跟 shell 无关。注意
lastcode = 0无条件执行,chdir 失败也是 0(缺陷)。export:
AddEnv(gargv[1]),把KEY=VALUE追加进 genv 表。下一轮 fork 出来的子进程就会带上这个变量------这就是环境变量"继承"的实现。
env:遍历打印 genv。
echo:
if (gargc == 2) { if (gargv[1][0] == '$' && gargv[1][1] == '?') printf("%d\n", lastcode); else printf("%s\n", gargv[1]); } else { printf("\n"); }
echo $?就是打印 lastcode。- 只支持单个参数:
echo a b c会走 else 只打印换行;echo $?x因为第二个字符是?也会被误判成$?(缺陷)。
八、main 主循环 + 已知缺陷清单
cppint main() { InitEnv(); // 启动时先拷贝环境 char cmd[basesize]; while (true) { PrintCommandLine(); if (!GetCommandLine(cmd, basesize)) continue; ParseCommandLine(cmd); if (CheckAndExecBuiltCommand()) continue; ExecuteCommand(); } }逻辑链:提示符 → 读 → 解析 → 内建?→ 是:continue 回到顶部;否:fork 执行外部命令 → 回到顶部。
这个教学版代码里隐藏着几个真实缺陷,值得你知道(面试常被问):
# 缺陷 原因 后果 1 Ctrl+D 死循环 EOF 时 GetCommandLine返回 false,main 里却continue回到循环顶部,下一次 fgets 又 EOF刷屏死循环,Ctrl+D 根本退不出去(应改为 break 或区分 EOF/空行) 2 纯空格输入段错误 strtok返回 NULL,gargv[0]=NULL,随后strcmp(NULL,...)直接崩 3 PWD 不同步 GetPwd用putenv更新的是真实 environ,而传给子进程的是 genv(启动时拷贝的旧值)cd之后外部命令看到的$PWD仍是 shell 启动时的值4 argv 溢出 ParseCommandLine不检查gargc < argvnum超 64 个参数越界写 5 export 越界 AddEnv不检查 genv 容量export 超 64 次越界写 6 echo a b c只输出空行echo 只处理 gargc==2多参数被吞 7 cd 失败也 lastcode=0 lastcode = 0无条件执行$?不反映真实失败
九、编译注意
- 编译命令:
g++ -o mini_shell mini_shell.cpp -std=c++11execvpe是 GNU 扩展,声明在unistd.h的#ifdef __USE_GNU块里。如果编译报execvpe was not declared,需要加宏:g++ -o mini_shell mini_shell.cpp -std=c++11 -D_GNU_SOURCE- 它依赖 Linux/Unix 环境(fork/exec/waitpid 都是 POSIX 系统调用),Windows 上无法直接编译运行。
十、函数复习
cppC 语言 `<string.h>` 中以 `str` 开头的函数(C 风格字符串): 表格 | 函数 | 作用 | | --- | --- | | `strlen(s)` | 求长度(不含 `\0`) | | `strcpy(dest, src)` | 复制字符串(不检查容量,危险) | | `strncpy(dest, src, n)` | 复制最多 n 个字符 | | `strcat(dest, src)` | 拼接(不检查容量,危险) | | `strncat(dest, src, n)` | 拼接最多 n 个字符 | | `strcmp(s1, s2)` | 比较(<0、0、>0) | | `strncmp(s1, s2, n)` | 比较前 n 个字符 | | `strchr(s, c)` | 正向找字符,返回指针,找不到返回 `NULL` | | `strrchr(s, c)` | 反向找字符 | | `strstr(s, sub)` | 找子串,返回子串首地址指针 | | `strtok(s, delim)` | 按分隔符切分字符串 | | `strdup(s)` | 复制一份新字符串(需 `free`) | | `strspn(s, accept)` | 返回 s 开头连续属于 accept 的字符数 | | `strcspn(s, reject)` | 返回 s 开头连续不属于 reject 的字符数 | | `strerror(errno)` | 把错误码转成错误信息字符串 | 常用示例: ``` #include <string.h> char s1[] = "hello"; char s2[20]; strcpy(s2, s1); // s2 = "hello" strcat(s2, " world"); // s2 = "hello world" if (strcmp(s1, "hello") == 0) { // 相等 } char *p = strstr(s2, "wor"); // 指向 "world" 开头 char *c = strchr(s2, 'o'); // 指向第一个 'o' // strtok 切分 char s[] = "apple,banana,cherry"; char *tok = strtok(s, ","); while (tok) { printf("%s\n", tok); // apple / banana / cherry tok = strtok(NULL, ","); } ```
函数 输出目标 安全版本 说明 printf标准输出 --- 最常用 fprintf指定文件流 --- fprintf(stderr, ...)打错误sprintf字符缓冲区 sprintf_s不检查容量,会溢出,慎用 snprintf字符缓冲区(限长) snprintf_s推荐,安全 asprintf自动分配缓冲区 --- GNU 扩展,自动 malloc,需 free dprintf文件描述符 fd --- POSIX,直接写 fd sprintf_s字符缓冲区 --- MSVC 安全版,验证参数 snprintf_s字符缓冲区(限长) --- MSVC 安全版 vprintf标准输出 --- 接受 va_listvfprintf文件流 --- 接受 va_listvsprintf缓冲区 vsprintf_s不检查容量 vsnprintf缓冲区(限长) vsnprintf_s接受 va_list的 snprintf版本一:C语言
cpp#include "myshell.h" #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/wait.h> // 提示符(真的是字符串)相关 static char username[32]; static char hostname[64]; static char cwd[256]; static char commandline[256]; // 命令行相关 static char *argv[64]; // 一般最多也就十几个 static int argc = 0; static const char *sep = " "; // 与退出码有关 static int lastcode; // 与环境变量有关,应该由bash来维护,从系统配置文件读,直接从系统bash拷贝即可 static char *env[64]; static void GetUserName() { char *_username = getenv("USER"); // 获取环境变量 strcpy(username,(_username ? _username : "None")); } static void GetHostName() { char *_hostname = getenv("HOSTNAME"); // 获取环境变量 strcpy(hostname,(_hostname ? _hostname : "None")); } //static void GetCwdName() // 要详细处理,包括只保留"/"之后的路径 //{ // char *_cwd = getenv("PWD"); // 获取环境变量 // strcpy(cwd,(_cwd ? _cwd : "None")); //} static void GetCwdName() // 要详细处理,包括只保留"/"之后的路径 { char _cwd[256]; getcwd(_cwd,sizeof(_cwd)); if(strcmp(_cwd,"/") == 0) { strcpy(cwd,_cwd); } else { int end = strlen(_cwd) - 1; while(end >= 0) { if(_cwd[end] == '/') { // 如 /home/Alice strcpy(cwd,&_cwd[end + 1]); break; } end--; } } } void PrintPrompt() { GetUserName(); GetHostName(); GetCwdName(); printf("[%s@%s %s]# ",username,hostname,cwd); fflush(stdout); // 直接刷新缓冲区 } static void GetCommandLine() { if(fgets(commandline,sizeof(commandline),stdin) != NULL) { // 比如"abcd\n" -> "abcd" commandline[strlen(commandline) - 1] = 0; // printf("debug:%s\n",commandline); } } static void ParseCommandLine() { // 清空 argc = 0; memset(argv,0,sizeof(argv)); // 判空 if(strlen(commandline) == 0) return; // 解析 ls -a -l argv[argc] = strtok(commandline,sep); // strtok分割字符串,变成几个子串 while((argv[++argc] = strtok(NULL,sep))); // argc的大小即子串个数 // // 调试信息注释掉 // printf("argc:%d\n",argc); // int i = 0; // for(;argv[i];i++) // { // printf("argv[%d]:%s\n",i,argv[i]); // } } // 创建子进程 void Execute() { pid_t id = fork(); if(id < 0) { perror("fork"); return; } else if(id == 0) { // printf("child running..\n"); // 子进程 // 程序替换 execvp(argv[0],argv); exit(1); } else{ // 父进程 int status = 0; pid_t rid = waitpid(id,&status,0); // 得到状态 (void)rid; lastcode = WEXITSTATUS(status); } } // 1:yes // 0:no int CheckBuiltinAndExcute() { int ret = 0; if(strcmp(argv[0],"cd") == 0) { // 内建命令 ret = 1; if(argc == 2) // 有选项 { chdir(argv[1]); } } else if(strcmp(argv[0],"echo") == 0) { ret = 1; if(argc == 2) { // echo $? // echo $PATH // echo "helloworld" // echo helloworld if(argv[1][0] == '$') { if(strcmp(argv[1],"$?") == 0) { printf("%d\n",lastcode); lastcode = 0; // 返回上一次的退出码 } else { // env:环境变量 } } else { printf("%s\n",argv[1]); // 打印字符串,不细分什么""或者没有""了 } } } return ret; } void Bash() { while(1) // 软件都是死循环的 { // 第1步:输出命令行 PrintPrompt(); // 第2步:等待用户输入,获取用户输入 // char commandline[256]; -> scanf() // sleep(1); // 等待一会儿 GetCommandLine(); // 第3步:解析字符串,"ls -a -l" -> "ls" "-a" "-l" ParseCommandLine(); if(argc == 0) continue; // 第4步:有些命令,cd、ench、env、export等命令,不应该让子进程执行,而应该让父进程自己执行,内建命令。bash内部的函数 if(CheckBuiltinAndExcute()) { continue; } // 第5步:执行命令 Execute(); } }版本二:C++
cpp/****************************************************************************** * mini_shell.cpp * 一个最小可用的 Linux Shell: * - 支持普通外部命令(ls / ps / whoami / pwd ...) * - 支持内建命令:cd / export / env / echo $? / exit * - 支持环境变量继承与维护 * - 支持 echo $? 打印上一条命令退出码 * 编译:g++ -o mini_shell mini_shell.cpp -std=c++11 * 运行:./mini_shell *****************************************************************************/ #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <ctype.h> using namespace std; /* ====================== 全局配置 ====================== */ const int basesize = 1024; // 命令行缓冲区大小 const int argvnum = 64; // argv 最多参数个数 const int envnum = 64; // 环境变量最多个数 /* ====================== 全局状态 ====================== */ static char *gargv[argvnum]; // 命令行参数表,末尾必须为 NULL static int gargc = 0; // 参数个数 static int lastcode = 0; // 上一条命令的退出码(echo $? 用) static char *genv[envnum]; // shell 自己维护的环境变量表 static char pwd[basesize]; // 当前工作目录 static char pwdenv[basesize];// "PWD=xxx" 字符串 /* ====================== 提示符相关 ====================== */ static string GetUserName() { const char *name = getenv("USER"); return name ? name : "None"; } static string GetHostName() { const char *host = getenv("HOSTNAME"); return host ? host : "None"; } static string GetPwd() { if (getcwd(pwd, sizeof(pwd)) == NULL) return "None"; snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd); putenv(pwdenv); return pwd; } static string LastDir() { string curr = GetPwd(); if (curr == "/" || curr == "None") return curr; size_t pos = curr.rfind("/"); if (pos == string::npos) return curr; return curr.substr(pos + 1); } static string MakeCommandLine() { char line[basesize]; snprintf(line, basesize, "[%s@%s %s]# ", GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str()); return line; } static void PrintCommandLine() { printf("%s", MakeCommandLine().c_str()); fflush(stdout); // 必须 fflush,因为提示符没 \n } /* ====================== 读命令行 ====================== */ static bool GetCommandLine(char buf[], int size) { if (fgets(buf, size, stdin) == NULL) { printf("\n"); // Ctrl+D 退出 return false; } buf[strlen(buf) - 1] = '\0'; // 去掉 fgets 读进来的 \n if (strlen(buf) == 0) return false; return true; } /* ====================== 解析命令行 ====================== */ static void ParseCommandLine(char buf[]) { memset(gargv, 0, sizeof(gargv)); gargc = 0; const char *sep = " "; gargv[gargc++] = strtok(buf, sep); while ((gargv[gargc++] = strtok(NULL, sep)) != NULL) ; gargc--; } /* ====================== 环境变量操作 ====================== */ static void InitEnv() { extern char **environ; int i = 0; for (; environ[i] && i < envnum - 1; i++) { genv[i] = (char *)malloc(strlen(environ[i]) + 1); strcpy(genv[i], environ[i]); } genv[i] = NULL; } static void AddEnv(const char *item) { int i = 0; while (genv[i]) i++; genv[i] = (char *)malloc(strlen(item) + 1); strcpy(genv[i], item); genv[++i] = NULL; } /* ====================== 外部命令执行 ====================== */ static void ExecuteCommand() { pid_t id = fork(); if (id < 0) { perror("fork"); lastcode = 1; return; } if (id == 0) { // 子进程:替换成外部程序 execvpe(gargv[0], gargv, genv); perror(gargv[0]); // exec 失败才会走到这 exit(127); } // 父进程:阻塞等子进程 int status = 0; waitpid(id, &status, 0); if (WIFEXITED(status)) lastcode = WEXITSTATUS(status); else if (WIFSIGNALED(status)) lastcode = 128 + WTERMSIG(status); // 被信号杀 else lastcode = 100; } /* ====================== 内建命令 ====================== */ static bool CheckAndExecBuiltCommand() { /* exit:退出 shell */ if (strcmp(gargv[0], "exit") == 0) { printf("bye~\n"); exit(0); } /* cd:改 shell 自己的工作目录 */ if (strcmp(gargv[0], "cd") == 0) { if (gargc == 2) { if (chdir(gargv[1]) != 0) perror("cd"); } else { chdir(getenv("HOME") ? getenv("HOME") : "/"); } lastcode = 0; return true; } /* export KEY=VALUE */ if (strcmp(gargv[0], "export") == 0) { if (gargc == 2) { AddEnv(gargv[1]); lastcode = 0; } else lastcode = 1; return true; } /* env:列环境变量 */ if (strcmp(gargv[0], "env") == 0) { for (int i = 0; genv[i]; i++) printf("%s\n", genv[i]); lastcode = 0; return true; } /* echo:支持 echo $? / echo xxx */ if (strcmp(gargv[0], "echo") == 0) { if (gargc == 2) { if (gargv[1][0] == '$' && gargv[1][1] == '?') printf("%d\n", lastcode); else printf("%s\n", gargv[1]); } else { printf("\n"); } lastcode = 0; return true; } return false; // 不是内建,去 fork } /* ====================== 主循环 ====================== */ int main() { InitEnv(); char cmd[basesize]; while (true) { PrintCommandLine(); if (!GetCommandLine(cmd, basesize)) continue; ParseCommandLine(cmd); if (CheckAndExecBuiltCommand()) continue; ExecuteCommand(); } return 0; }
WIFEXITED是<sys/wait.h>中的宏,判断子进程是否正常退出(通过exit()或return),配合wait()/waitpid()使用:
cpp#include <sys/wait.h> #include <unistd.h> #include <stdio.h> pid_t pid = fork(); if (pid == 0) { exit(42); // 子进程正常退出 } int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { printf("正常退出,退出码 = %d\n", WEXITSTATUS(status)); // 42 }wait 状态宏全家桶:
宏 作用 WIFEXITED(status)是否正常退出(exit/return) WEXITSTATUS(status)正常退出时的退出码(仅 WIFEXITED 为真时用) WIFSIGNALED(status)是否被信号杀死 WTERMSIG(status)杀死它的信号编号(仅 WIFSIGNALED 为真时用) WCOREDUMP(status)是否产生核心转储(非 POSIX 标准,部分系统有) WIFSTOPPED(status)是否因信号暂停(配合 WUNTRACED) WSTOPSIG(status)使其暂停的信号编号 WIFCONTINUED(status)是否被 SIGCONT 恢复(配合 WCONTINUED) 完整判断模板:
cppif (WIFEXITED(status)) { printf("正常退出,码 %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("被信号 %d 杀死\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { printf("被信号 %d 暂停\n", WSTOPSIG(status)); }核心点:
status是打包的位字段,不能直接比较,必须用宏解包。判断 "是否正常退出" 看
WIFEXITED,取退出码看WEXITSTATUS,两者通常成对出现。
【Linux】 自主shell编写巩固复习篇
Brilliantwxx2026-10-18 15:54
相关推荐
Qt云程序员3 小时前
零代码网页数据采集教程:不写爬虫,把网页数据搬进 Excel十年Java程序媛3 小时前
Java 多线程基础与锁机制详解|进程线程、锁升级、synchronized 与 Lock 对比弯_弯3 小时前
FDE 的工程能力画像:数据接入、PoC 与可复用资产的落地口径鱼很腾apoc3 小时前
【MySQL】第3期 索引、事务与视图系统详解雾沉川3 小时前
Visual C++运行库合集 v105.0 安装教程anew___3 小时前
《从零手写操作系统 (49):虚拟内存进阶——Demand Paging、Swap与缺页异常处理》迷途之人不知返3 小时前
【基础IO】-4-再谈重定向Android系统攻城狮4 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_parse_caps调用流程与实战(八十)l1t4 小时前
DeepSeek总结的 DuckDB 2.0 文档新内容

