C语言实现Linux Shell脚本调用的四种方法

落日邮差
落日邮差
发布时间:2026-10-14| 3人浏览过
原创

在Linux环境下进行C/C++开发时,直接调用外部Shell脚本是常见需求。本文总结了使用 system、popen、fork/exec 组合以及多线程技术来实现脚本调用的具体方法,并对比了各方案在执行效率、安全性及资源控制上的差异。

C语言调用Shell脚本的常用方案概述

在Linux系统中,C程序可以通过多种机制启动子进程执行外部脚本。主要方案包括直接调用库函数(如 system)、管道读取(如 popen)以及底层的进程创建与替换(fork 和 exec 系列)。选择合适的调用方式,需要在功能完整性、执行效率和安全性之间取得平衡。

方法一:使用 system() 函数

system() 是最简单直接的调用方式。它会派生一个子进程,调用 /bin/sh 来执行指定的字符串命令。

第1步:引入头文件并调用

需在代码中包含 #include <stdlib.h>,随后直接传入要执行的命令字符串。

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

int main() {
    // 执行脚本
    int ret = system("./my_script.sh");
    
    // 检查返回值
    if (ret == -1) {
        perror("system failed");
    } else {
        printf("Exit status: %d\n", WEXITSTATUS(ret));
    }
    return 0;
}

注意:安全性与阻塞问题

  • 阻塞: system() 会一直等待子进程结束才返回,不适合高频调用或需并行处理的场景。
  • 安全: 直接使用用户输入拼接命令存在 Shell Injection 风险。且其内部调用 /bin/sh -c,开销较大。

方法二:使用 popen() 获取脚本输出

如果不仅需要执行脚本,还需要获取脚本的标准输出结果,popen() 是最佳选择。它创建一个管道,将子进程的 stdout 连接到父进程的 stdin 或反之。

第1步:通过 popen 打开管道

设置模式为 "r" 读取输出,或 "w" 写入输入。

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

#define BUF_SIZE 1024

int main() {
    FILE *fp;
    char buffer[BUF_SIZE];
    
    // 打开只读管道
    fp = popen("./get_status.sh", "r");
    if (fp == NULL) {
        perror("popen failed");
        return 1;
    }
    
    // 读取脚本输出的数据
    while (fgets(buffer, BUF_SIZE, fp) != NULL) {
        printf("Output: %s", buffer);
    }
    
    // 关闭管道
    pclose(fp);
    return 0;
}

特点:I/O 交互优势

  • 非阻塞流: 适合处理大量结构化文本输出。
  • 局限性: 无法获取子进程的退出码(只能从 pclose 返回的状态中推断),且主要用于单方向的数据传输。

方法三:基于 fork() + exec() 的高级控制

对于需要精细控制环境变量、进程组或标准描述符的场景,必须使用底层的 fork() 配合 exec() 族函数。这是 system() 的内部实现原理,但去除了不必要的壳层开销。

第1步:创建子进程

调用 fork() 后,父子进程继续执行,利用返回值区分当前环境。

第2步:在子进程中替换程序

子进程调用 execvp() 加载新程序。注意,exec 成功后当前代码段将被丢弃,不会返回。

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

int run_script(char *script_path, char *args[]) {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        return -1;
    }
    
    if (pid == 0) {
        // 子进程
        // 建议使用 execlp,自动搜索 PATH
        if (execlp(script_path, script_path, (char *)NULL) == -1) {
            perror("exec failed");
            exit(1);
        }
    } else {
        // 父进程
        int status;
        waitpid(pid, &status, 0); // 等待子进程结束
        return WEXITSTATUS(status);
    }
    return 0;
}

第3步:处理状态码

父进程通过 waitpid 获取子进程的退出状态,使用 WEXITSTATUS() 宏提取实际退出码。

方法四:多线程中调用系统命令

如果在多线程程序中直接调用 system(),可能会引发信号处理冲突或死锁,因为某些信号处理在多进程中行为不一致。

操作顺序:封装线程安全调用

  1. 若只需运行不关心输出,建议使用 fork+exec+waitpid 组合替代 system。
  2. 若必须使用 system,请确保主程序未安装复杂的异步信号处理 handler,或者在调用前禁用/恢复相关信号。
  3. 推荐使用 POSIX 标准的 posix_spawn() 接口(若系统支持),它在多进程间更安全且高效。

方案对比总结

方法优点缺点推荐场景
system()语法极简不安全,开销大,阻塞快速原型开发,简单的一次性命令
popen()天然支持 I/O 读写无法直接获取 Exit Code,仅限单向流捕获命令输出进行处理
fork+exec()完全控制,效率高,可定制环境代码冗长,需手动管理生命周期生产级应用,复杂参数传递,性能敏感

以上就是在C语言中调用Linux脚本的详细方法的整理,更多关于Linux编程进阶资料的关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。