杰瑞科技汇

Python如何在Linux中调用C命令?

调用一个独立的 C 程序(可执行文件)

这是最常见的情况,你已经有一个编译好的 C 程序(my_c_program),你想在 Python 脚本中执行它并获取其输出。

方法 1:使用 os.system() (最简单但不推荐)

这是最直接的方法,但它会将命令的控制权交给子 shell,无法获取程序的返回值或标准输出/错误流。

C 代码 (hello.c):

#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
    printf("Hello from C program!\n");
    printf("Received %d arguments.\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("Arg %d: %s\n", i, argv[i]);
    }
    sleep(2); // 模拟耗时操作
    return 42; // 返回一个状态码
}

编译 C 程序:

gcc -o hello hello.c

Python 代码 (call_os_system.py):

import os
# 执行命令,无法获取输出或返回码
os.system("./hello")
# 可以传递参数
# os.system("./hello arg1 arg2")

缺点:

  • 无法获取输出os.system 执行后,你不知道 C 程序打印了什么。
  • 无法获取返回码:无法知道 C 程序的 main 函数返回的值(return 42)。
  • 安全性问题:如果命令字符串来自不可信的输入,容易引发命令注入攻击。
  • 交互性差:无法向 C 程程的标准输入发送数据。

方法 2:使用 subprocess 模块 (强烈推荐)

subprocess 是 Python 官方推荐的用于创建子进程的模块,功能强大且灵活,它替代了旧的 os.system, os.spawn*, popen2commands 模块。

1 subprocess.run() (Python 3.5+ 推荐)

这是最现代、最推荐的接口。

Python 代码 (call_subprocess_run.py):

import subprocess
# --- 场景1: 执行命令并获取输出 ---
try:
    # 执行命令,捕获标准输出和标准错误
    # text=True 会自动将输出解码为字符串
    # check=True 会在命令返回非零状态码时抛出 CalledProcessError 异常
    result = subprocess.run(
        ["./hello", "arg1", "arg2"],
        capture_output=True,
        text=True,
        check=True,
        timeout=5 # 设置超时时间
    )
    # 打印 C 程序的返回码
    print(f"Return code: {result.returncode}")
    # 打印 C 程序的标准输出
    print("Standard Output:")
    print(result.stdout)
    # check=True 且命令失败,这里可以打印标准错误
    # 但如果 check=True 且命令成功,stderr 可能为空
    if result.stderr:
        print("Standard Error:")
        print(result.stderr)
except subprocess.CalledProcessError as e:
    print(f"Command failed with return code {e.returncode}")
    print(f"Output: {e.output}")
    print(f"Error: {e.stderr}")
except subprocess.TimeoutExpired as e:
    print(f"Command timed out: {e.cmd}")
    print(f"Output: {e.output}")
# --- 场景2: 不关心输出,只关心是否成功 ---
subprocess.run(["./hello"], check=True) # 如果失败,会抛出异常
print("Command executed successfully.")
# --- 场景3: 不想捕获输出,直接打印到终端 (类似 os.system) ---
# shell=True 可以使用 shell 特性,但要注意安全风险
subprocess.run("./hello arg1 arg2", shell=True)

优点:

  • 功能全面:可以获取返回码、标准输出、标准错误。
  • 安全:推荐使用列表形式传递参数 (["./hello", "arg1"]),可以有效防止命令注入。
  • 灵活:支持超时、工作目录、环境变量等高级选项。
  • 代码清晰run() 是一个同步调用,代码逻辑直观。

2 subprocess.Popen() (更底层,更灵活)

当你需要与子进程进行更复杂的交互时(持续读取输出、向其写入输入),Popen 是更好的选择,它创建一个进程对象,然后你可以手动操作它。

Python 代码 (call_subprocess_popen.py):

import subprocess
# 使用 Popen 启动进程
# stdout=subprocess.PIPE 表示捕获标准输出
# stderr=subprocess.PIPE 表示捕获标准错误
# text=True 表示以文本模式处理输入输出
proc = subprocess.Popen(
    ["./hello", "from_popen"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)
# wait() 等待进程结束
return_code = proc.wait()
# 获取输出
stdout, stderr = proc.communicate() # communicate() 会读取所有输出并等待进程结束
print(f"Popen Return code: {return_code}")
print("Popen Standard Output:")
print(stdout)
if stderr:
    print("Popen Standard Error:")
    print(stderr)
# --- 实时读取输出的例子 ---
print("\n--- Real-time output example ---")
proc = subprocess.Popen(["./hello", "realtime"], stdout=subprocess.PIPE, text=True, bufsize=1)
# 实时读取每一行
for line in proc.stdout:
    print(f"[PYTHON] Received: {line.strip()}")
# 确保进程已经结束
proc.wait()
print(f"Realtime process finished with code: {proc.returncode}")

优点:

  • 异步能力:可以启动进程后,不立即等待其结束,而是继续执行其他代码。
  • 交互能力强:可以持续读写子进程的 I/O 流。
  • 更底层控制:提供了 poll(), send_signal(), terminate(), kill() 等方法来管理进程。

在 Python 中直接调用 C 函数 (无需独立程序)

如果你不想创建一个独立的可执行文件,而是想直接调用 C 代码中定义的函数,你需要使用 Python 的 C 扩展机制,这通常通过 ctypesCFFI 库来实现。

方法 3:使用 ctypes 库 (无需编译)

ctypes 是 Python 的一个标准库,允许你调用动态链接库(.so 文件)中的 C 函数。

C 代码 (mylib.c):

#include <stdio.h>
// 一个简单的加法函数
int add(int a, int b) {
    printf("C function 'add' called with %d and %d\n", a, b);
    return a + b;
}
// 一个返回字符串的函数
const char* get_message() {
    printf("C function 'get_message' called\n");
    return "Hello from C library!";
}

编译成共享库 (.so 文件):

# -fPIC 生成位置无关代码,用于共享库
# -shared 指定生成共享库
gcc -fPIC -shared -o mylib.so mylib.c

Python 代码 (call_ctypes.py):

import ctypes
import os
# 加载共享库
# 使用绝对路径或确保 mylib.so 在当前目录下
lib_path = os.path.join(os.path.dirname(__file__), "mylib.so")
mylib = ctypes.CDLL(lib_path)
# --- 调用 add(int, int) 函数 ---
# 1. 告诉 Python C 函数的参数和返回类型
mylib.add.argtypes = [ctypes.c_int, ctypes.c_int]
mylib.add.restype = ctypes.c_int
# 2. 调用函数
result = mylib.add(10, 20)
print(f"Result from add(10, 20): {result}")
# --- 调用 get_message() 函数 ---
# 1. 告诉 Python C 函数的返回类型是 C 字符串
mylib.get_message.argtypes = [] # 无参数
mylib.get_message.restype = ctypes.c_char_p # 返回 C 风格的字符串
# 2. 调用函数
# ctypes 会自动将 C 字符串转换为 Python 字符串
c_string = mylib.get_message()
print(f"Result from get_message(): {c_string}")
# 记得释放 C 字符串占用的内存(如果需要)
# mylib.get_message.restype = ctypes.c_void_p # 如果返回的是 malloc 的内存
# c_string_ptr = mylib.get_message()
# print(f"Result: {ctypes.c_char_p(c_string_ptr).value}")
# ctypes.pythonapi.PyMem_Free(c_string_ptr)

优点:

  • 高性能:直接调用 C 函数,没有进程创建和通信的开销。
  • 无缝集成:C 函数可以像 Python 函数一样被调用。
  • 无需修改 C 代码:只要能编译成 .so 库就可以。

缺点:

  • 类型管理:需要手动声明 C 的数据类型和 Python 数据类型的映射,容易出错。
  • 内存管理:处理 C 分配的内存(如 malloc)需要小心,避免内存泄漏。
  • 平台相关.so 文件是 Linux 下的,Windows 下是 .dll

总结与选择

方法 适用场景 优点 缺点
os.system 简单、快速执行命令,不关心结果 极其简单 无法获取输出/返回码,不安全,交互性差
subprocess.run 强烈推荐,执行外部程序并获取结果 功能强大,安全,代码清晰,推荐用于大多数情况 相比 os.system 代码稍多
subprocess.Popen 需要高级进程控制(如实时交互、异步) 灵活,交互能力强,功能最全面 API 更复杂,代码量更多
ctypes 需要直接调用 C 库中的函数,而不是执行整个程序 高性能,无缝集成,无需独立进程 类型管理复杂,内存管理需小心,平台相关

如何选择?

  1. 如果你的 C 代码是一个独立的命令行工具,并且你想在 Python 脚本中运行它:

    • 首选 subprocess.run(),它提供了你需要的所有功能(获取输出、返回码、处理错误),并且是现代 Python 的标准方式。
    • 如果需要实时读取输出或进行更复杂的交互,使用 subprocess.Popen()
  2. 如果你的 C 代码是一组函数,你想把它们作为 Python 的一部分来使用(为了计算密集型任务加速):

    • 使用 ctypes,将 C 代码编译成 .so 库,然后在 Python 中加载并调用其中的函数,这是实现 Python 和 C 混合编程的高效方式。
分享:
扫描分享到社交APP
上一篇
下一篇