system() 函数是 C 标准库 <stdlib.h> 中的一个函数,用于执行操作系统命令,它的行为和返回值在不同操作系统(如 Windows 和 Unix-like 系统)上有所不同。

函数原型
int system(const char *command);
- 参数
command: 一个指向以 null 结尾的字符串的指针,该字符串包含了要执行的操作系统命令。command是一个空指针(NULL),system()函数不会执行任何命令,而是会返回一个非零值,表示当前系统命令处理程序(shell)是可用的。
- 返回值: 一个
int类型的值,其含义取决于command是否为NULL以及执行命令的结果。
返回值的详细解释
system() 函数的返回值可以分为两种主要情况:当 command 为 NULL 时,和当 command 不为 NULL 时。
command 为 NULL
当 system(NULL) 被调用时,它不执行任何命令,而是测试命令解释器(shell)是否存在且可用。
- 返回值:
- 非零值: 表示系统命令处理程序(shell)可用,在 Linux 上通常会返回
1。 0: 表示系统命令处理程序不可用。- 其他值: 行为实现定义,但通常表示错误。
- 非零值: 表示系统命令处理程序(shell)可用,在 Linux 上通常会返回
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
if (system(NULL)) {
printf("Shell is available.\n");
} else {
printf("Shell is not available.\n");
}
return 0;
}
command 不为 NULL (这是最常见的情况)
当 system(command) 被调用时,它会启动一个子进程来执行指定的 command。system() 函数会等待该命令执行完毕,然后返回一个状态码。

这个返回值并不是命令本身的退出码,而是一个经过包装的值,为了理解它,我们需要分两步看:
-
底层实现:
system()函数的实现大致如下:- 调用
fork()创建一个子进程。 - 在子进程中,调用
execl()来执行command。 - 父进程调用
waitpid()或类似的函数等待子进程结束。 - 当子进程结束时,它会向父进程发送一个终止状态,这个状态包含了子进程是如何结束的(正常退出、被信号杀死等)以及它的退出码。
- 调用
-
system()的返回值:system()函数会将这个子进程的终止状态进行解码,然后返回一个特定的整数值,这个整数值的结构如下:- 最低位(第7位): 子进程是否因信号而异常终止。
- 如果为
1,表示子进程被信号终止。 - 如果为
0,表示子进程正常退出。
- 如果为
- 高8位(第8-15位): 子进程的退出码。
这意味着,
system()的返回值包含了两个信息:是正常退出还是被信号终止,以及退出码是多少。
(图片来源网络,侵删) - 最低位(第7位): 子进程是否因信号而异常终止。
如何正确解析返回值?
直接检查 system() 的返回值是否为 0 是错误的。exit(1) 命令会让 system() 返回 33824(在 Linux 上,1 << 8 | 0),而不是 1,直接判断 system("exit 1") == 0 会得到错误的结果。
正确的做法是使用宏 WEXITSTATUS 和 WIFSIGNALED 来解析这个返回值,这些宏定义在 <sys/wait.h> 头文件中。
解析步骤:
- 检查是否正常退出: 使用
WIFEXITED(status)宏。如果宏返回真(非零),表示子进程正常退出。
- 获取退出码: 如果子进程正常退出,使用
WEXITSTATUS(status)宏来获取实际的退出码。
示例代码 (Linux/macOS 环境)
下面的例子展示了如何在不同情况下解析 system() 的返回值。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h> // 用于 WIFEXITED 和 WEXITSTATUS
void check_system_call(const char *command) {
printf("Executing command: \"%s\"\n", command);
int status = system(command);
if (status == -1) {
perror("system() call failed");
return;
}
// WIFEXITED(status) 为真,表示子进程正常退出
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
printf("Command exited normally with code: %d\n", exit_code);
}
// WIFSIGNALED(status) 为真,表示子进程被信号终止
else if (WIFSIGNALED(status)) {
int signal_num = WTERMSIG(status);
printf("Command was killed by signal: %d\n", signal_num);
} else {
printf("Command terminated abnormally (unknown reason).\n");
}
printf("----------------------------------------\n");
}
int main() {
// 1. 命令成功执行,退出码为 0
check_system_call("ls /etc/passwd");
// 2. 命令执行失败,退出码为 1 (ls一个不存在的目录)
check_system_call("ls /non_existent_directory");
// 3. 命令不存在,返回 127
check_system_call("this_command_does_not_exist");
// 4. 命令被信号终止 (使用 Ctrl+C)
// 注意:在终端中运行时,Ctrl+C 会发送 SIGINT (2) 给前台进程组。
// 但 system() 启动的子进程在 shell 中,shell 会处理这个信号。
// 为了演示,我们用 kill 命令来杀掉它自己,这有点绕,但能说明问题。
// 更好的例子是让子进程进入死循环,然后我们手动 kill 它。
// 这里用一个简单的例子来展示 WIFSIGNALED 的用法
// check_system_call("sleep 10 & kill %1"); // 这个例子在脚本中更直观
return 0;
}
可能的输出:
Executing command: "ls /etc/passwd"
/etc/passwd:
Command exited normally with code: 0
----------------------------------------
Executing command: "ls /non_existent_directory"
ls: cannot access '/non_existent_directory': No such file or directory
Command exited normally with code: 2
----------------------------------------
Executing command: "this_command_does_not_exist"
sh: 1: this_command_does_not_exist: not found
Command exited normally with code: 127
----------------------------------------
Windows 平台的特殊性
在 Windows 上,system() 函数的行为略有不同:
command为NULL: 返回非零值,表示命令解释器(通常是cmd.exe)可用。command不为NULL:- 如果命令成功执行且返回退出码
0,system()返回0。 - 如果命令执行失败或返回非零退出码,
system()返回该退出码。 system()调用本身失败(内存不足),它会返回-1,并设置errno。
- 如果命令成功执行且返回退出码
| 平台 | command 为 NULL |
command 不为 NULL (成功) |
command 不为 NULL (失败) |
|---|---|---|---|
| Unix-like | 非 0 | 包装后的状态码 (需用宏解析) | 包装后的状态码 (需用宏解析) |
| Windows | 非 0 | 0 |
命令的退出码 (通常是 > 0) |
最佳实践
- 总是检查
system()的返回值是否为-1,这表示system()函数调用本身出现了错误(无法创建子进程)。 - 不要直接判断返回值是否为
0来判断命令是否成功,在 Unix-like 系统上,这会忽略掉命令本身返回的非零退出码。 - 在 Unix-like 系统上,始终使用
WIFEXITED和WEXITSTATUS宏来正确解析返回的状态码,以获得命令的真实执行结果。 - 注意安全性:
system()函数存在安全风险。command字符串来自用户输入,攻击者可能注入恶意命令,如果用户输入"; rm -rf /;",system("ls " + user_input)就会变成system("ls ; rm -rf /;"),造成灾难性后果,对于不受信任的输入,应避免使用system()。
