文章目录 system函数 exec函数族 核心功能 函数原型 应用例程 execl() - 参数列表形式 execv() - 参数数组形式 execle() - 自定义环境变量 execve() - 系统调用(最底层) execlp() - 自动搜索PATH execvp() - PATH搜索 + 参数数组 标准模式:fork + exec system函数 函数原型 # include <stdlib.h> int system ( const char * command) ; 功能 system ( "ls -la" ) ; // 执行ls命令 system ( "echo Hello" ) ; // 执行echo命令 system ( "gcc test.c" ) ; // 执行编译命令 参数 有效命令:执行命令 NULL:检查shell是否可用 空字符串:""直接返回成功 # include <stdlib.h> # include <stdio.h> int main ( ) { // 1. 执行简单命令 system ( "pwd" ) ; // 打印当前目录 // 2. 执行带参数的命令 system ( "ls -l /tmp" ) ; // 3. 执行复杂命令(管道、重定向等) system ( "ps aux | grep bash > output.txt" ) ; // 4. 使用变量构建命令 char filename[ 100 ] = "test.txt" ; char command[ 200 ] ; snprintf ( command, sizeof ( command) , "cat %s" , filename) ; system ( command) ; return 0 ; } 返回值 == 127:启动shell失败,/bin/sh 不存在或不可执行 == -1:其他错误,fork失败、内存不足等 其他值:命令执行状态,shell的退出状态编码 int ret= system ( command) ; if ( ret== - 1 ) { // system()调用本身失败 perror ( "system() failed" ) ; } else if ( WIFEXITED ( ret) ) { // 命令正常结束 printf ( "命令退出码: %d\n" , WEXITSTATUS ( ret) ) ; if ( WEXITSTATUS ( ret) == 127 ) { printf ( "错误: shell无法启动或命令不存在\n" ) ; } else if ( WEXITSTATUS ( ret) == 126 ) { printf ( "错误: 命令不可执行\n" ) ; } } else if ( WIFSIGNALED ( ret) ) { // 命令被信号终止 printf ( "命令被信号终止: %d\n" , WTERMSIG ( ret) ) ; } 返回值获取shell退出状态 // 要获取真正的退出状态,需要处理返回值 int status= system ( "ls /nonexistent" ) ; if ( status== - 1 ) { printf ( "system()调用失败\n" ) ; } else { // 使用wait相关的宏 if ( WIFEXITED ( status) ) { int exit_code= WEXITSTATUS ( status) ; printf ( "命令退出码: %d\n" , exit_code) ; // ls /nonexistent 会返回 2 } } 实现原理 system("ls -l") ↓ fork() 创建子进程 ↓ 子进程:execl("/bin/sh", "sh", "-c", "ls -l", NULL) ↓ shell解析命令:ls -l ↓ shell执行ls命令 ↓ shell退出,返回ls的退出状态 ↓ 父进程接收状态,返回给调用者// system() 大致相当于以下代码: int system ( const char * command) { if ( command== NULL ) { // 检查shell是否存在 return shell_exists ( ) ? 1 : 0 ; } pid_t pid= fork ( ) ; if ( pid== 0 ) { // 子进程:执行shell execl ( "/bin/sh" , "sh" , "-c" , command, ( char * ) NULL ) ; _exit ( 127 ) ; // 如果execl失败 } else if ( pid> 0 ) { // 父进程:等待子进程结束 int status; waitpid ( pid, & status, 0 ) ; return status; // 返回shell的退出状态 } else { // fork失败 return - 1 ; } } 安全风险 system运行任意的shell命令 # include <stdio.h> # include <stdlib.h> int main ( int argc, const char * argv[ ] ) { char buf[ 1024 ] = { } ; while ( 1 ) { printf ( "命令->" ) ; fgets ( buf, 1024 , stdin ) ; system ( buf) ; } return 0 ; } 命令注入漏洞 // ⚠️ 危险!可能被攻击! char user_input[ 100 ] ; printf ( "输入文件名: " ) ; fgets ( user_input, sizeof ( user_input) , stdin ) ; char command[ 200 ] ; sprintf ( command, "rm %s" , user_input) ; // ⚠️ 危险! system ( command) ; // 如果用户输入: "test.txt; rm -rf /" // 实际执行: rm test.txt; rm -rf / exec函数族 核心功能 进程替换不创建新进程:替换当前进程的代码和数据 继承PID:保持原进程ID不变 全新开始:从新程序的main()函数开始执行 函数原型 // 所有exec函数都在 <unistd.h> 中 # include <unistd.h> int execl ( const char * path, const char * arg0, . . . , ( char * ) NULL ) ; int execv ( const char * path, char * const argv[ ] ) ; int execle ( const char * path, const char * arg0, . . . , ( char * ) NULL , char * const envp[ ] ) ; int execve ( const char * path, char * const argv[ ] , char * const envp[ ] ) ; int execlp ( const char * file, const char * arg0, . . . , ( char * ) NULL ) ; int execvp ( const char * file, char * const argv[ ] ) ; 命名规律
execl - l: list (参数列表) execv - v: vector (参数数组) execle - e: environment (传递环境变量) execlp - p: PATH (在PATH中查找程序) execvp - p + v: PATH + 参数数组 execve - 系统调用 (v + e)参数对比 函数 程序路径 参数传递方式 环境变量 PATH搜索 execl()完整路径 参数列表 继承当前 否 execv()完整路径 参数数组 继承当前 否 execle()完整路径 参数列表 自定义 否 execve()完整路径 参数数组 自定义 否 execlp()文件名 参数列表 继承当前 是 execvp()文件名 参数数组 继承当前 是
返回值 执行成功:不返回(进程被替换) 执行失败:返回 -1,设置 errno // 重要:exec成功时,后面的代码不会执行! if ( execl ( "/bin/ls" , "ls" , NULL ) == - 1 ) { // 只有exec失败时才会执行到这里 perror ( "exec failed" ) ; exit ( 1 ) ; } // exec成功后,这里的代码永远不会执行 printf ( "这行不会打印!\n" ) ; 常见错误码 # include <errno.h> int ret= execvp ( "nonexistent" , args) ; if ( ret== - 1 ) { switch ( errno) { case ENOENT: printf ( "程序不存在\n" ) ; break ; case EACCES: printf ( "没有执行权限\n" ) ; break ; case ENOMEM: printf ( "内存不足\n" ) ; break ; case E2BIG: printf ( "参数列表太长\n" ) ; break ; case ENOEXEC: printf ( "不是可执行文件\n" ) ; break ; default : perror ( "exec失败" ) ; } exit ( 1 ) ; } 应用例程 execl() - 参数列表形式 int execl ( const char * path, const char * arg0, . . . , ( char * ) NULL ) ; // 参数以列表形式传递,以NULL结束 // 示例:执行 ls -l /tmp execl ( "/bin/ls" , "ls" , "-l" , "/tmp" , NULL ) ; // 相当于终端命令:$ ls -l /tmp execv() - 参数数组形式 int execv ( const char * path, char * const argv[ ] ) ; // 参数以数组形式传递 // 示例:执行 ls -l /tmp char * args[ ] = { "ls" , "-l" , "/tmp" , NULL } ; execv ( "/bin/ls" , args) ; // argv数组必须以NULL结束! execle() - 自定义环境变量 int execle ( const char * path, const char * arg0, . . . , ( char * ) NULL , char * const envp[ ] ) ; // 示例:执行程序并传递新环境变量 char * env[ ] = { "MYVAR=hello" , "PATH=/usr/bin" , NULL } ; execle ( "/bin/sh" , "sh" , "-c" , "echo $MYVAR" , NULL , env) ; execve() - 系统调用(最底层) int execve ( const char * path, char * const argv[ ] , char * const envp[ ] ) ; // 示例:完全控制参数和环境变量 char * args[ ] = { "myprog" , "-v" , NULL } ; char * env[ ] = { "DEBUG=1" , "LOG_LEVEL=debug" , NULL } ; execve ( "/usr/local/bin/myprog" , args, env) ; execlp() - 自动搜索PATH int execlp ( const char * file, const char * arg0, . . . , ( char * ) NULL ) ; // 示例:不需要完整路径 execlp ( "ls" , "ls" , "-l" , "/tmp" , NULL ) ; // 系统会在PATH中查找ls // 相当于:execl("/bin/ls", "ls", "-l", "/tmp", NULL); execvp() - PATH搜索 + 参数数组 int execvp ( const char * file, char * const argv[ ] ) ; // 示例:最常用的组合 char * args[ ] = { "gcc" , "test.c" , "-o" , "test" , NULL } ; execvp ( "gcc" , args) ; // 自动在PATH中找gcc 标准模式:fork + exec # include <unistd.h> # include <sys/wait.h> # include <stdio.h> # include <stdlib.h> int main ( ) { pid_t pid= fork ( ) ; if ( pid== - 1 ) { perror ( "fork失败" ) ; exit ( 1 ) ; } if ( pid== 0 ) { // 子进程:执行新程序 printf ( "子进程 PID=%d 准备执行新程序\n" , getpid ( ) ) ; // 方法1:使用execl execl ( "/bin/ls" , "ls" , "-l" , "/tmp" , NULL ) ; // 方法2:使用execvp // char *args[] = {"ls", "-l", "/tmp", NULL}; // execvp("ls", args); // 如果exec失败 perror ( "exec失败" ) ; _exit ( 1 ) ; // 子进程结束 } else { // 父进程:等待子进程 printf ( "父进程 PID=%d 等待子进程\n" , getpid ( ) ) ; int status; waitpid ( pid, & status, 0 ) ; if ( WIFEXITED ( status) ) { printf ( "子进程退出码: %d\n" , WEXITSTATUS ( status) ) ; } } return 0 ; } 资源在exec后保持不变:进程ID和父进程ID 实际用户ID和实际组ID 进程组ID和会话ID 控制终端 当前工作目录 文件创建掩码 文件锁 未处理的闹钟 信号处理方式(但被忽略的信号保持忽略) 资源在exec后变化:代码段、数据段、堆栈(全新) 文件描述符的close-on-exec标志 共享内存、内存映射文件 线程(所有线程终止)