【C语言】C语言中执行命令

在C语言编程中,执行命令通常是通过调用库函数完成的。以下是一些C语言中用来执行系统命令的函数:

1. system():

这是C语言标准库函数之一,能够执行命令行命令。它调用操作系统的命令处理器来执行给定的命令。

#include <stdlib.h>

int main() {
    int ret;
    ret = system("ls -l"); // 假设是在Unix系统上,列出当前目录的详细信息
    if (ret == -1) {
        // 错误处理
    }
    return 0;
}

2. exec() 系列函数:

`exec()` 函数族提供了多种替换当前进程映像为新程序的方法。这些函数包括 execl(), execle(), execlp(), execv(), execvp() 和 execvpe(),用于加载并执行新的程序。

#include <unistd.h>

int main() {
    char *args[] = {"/bin/ls", "-l", NULL};
    execv("/bin/ls", args);
    // 如果execv成功,以下代码不会执行,因为整个程序已被替换
    perror("execv failed");
    return 1;
}

在操作系统中,一个进程是一个执行中的程序的实例,包括它的代码、数据、堆栈,以及与执行状态相关的其他信息,如程序计数器、寄存器等。进程映像(Process Image)是指进程在内存中的表现,即构成进程的所有信息。
exec() 函数族被用于在一个已存在的进程内部执行一个新的程序。具体来说,当调用`exec()`函数时,当前进程的代码和数据段会被新程序的代码和数据段所替换,但是进程的ID和其余的某些属性(如文件描述符)保持不变。这意味着,`exec()`函数不会创建一个新的进程,而是在原有进程的基础上更改它的执行内容。
替换后,新的程序将开始执行,而旧的程序将不再运行。`exec()`函数替换后,原来程序的所有代码和数据都将被新程序替换掉,并且不可恢复,执行流跳转到新的程序入口点开始执行。这是通过系统调用实现的,使用`exec()`函数替换的进程会继续保持原有的进程ID(PID)。
例如,如果在一个终端窗口里运行一个叫做`prog1`的程序,该程序在某个时刻调用`exec()`来运行`prog2`,那么`prog1`将会停止执行,其进程映像会被`prog2`所替换,该进程的PID保持不变,但从用户的角度看起来就好像`prog1`变成了`prog2`。
这个特性在很多 Unix 工具和服务器守护进程中与`fork()`系统调用一起使用,允许它们在创建新进程后立即替换为不同的执行代码,这是实现多任务和进程间通信的一种常见方法。

3. popen() 和 pclose():

这对函数用来创建一个进程,执行一个命令,打开一个用于读取或写入的管道,并最终关闭它。

#include <stdio.h>

int main() {
    FILE *fp;
    char path[1035];

    fp = popen("/bin/ls", "r");
    if (fp == NULL) {
        printf("Failed to run command\n" );
        exit(1);
    }

    while (fgets(path, sizeof(path) - 1, fp) != NULL) {
        printf("%s", path);
    }

    pclose(fp);
    
    return 0;
}

注意,使用这些命令时,程序将会具有执行任意操作系统命令的能力,这可能带来安全风险,所以应谨慎使用。特别是,应避免直接在命令中使用用户输入的字符串,因为这可能导致命令注入攻击。

在计算机科学中,管道是一种特定的进程间通信(IPC)机制,它允许一个进程的输出成为另一个进程的输入。传统的管道通常是半双工的(单向的),这意味着数据只能在一个方向上流动。
在UNIX和类UNIX系统中,管道是一个很常见的功能,用来将两个或更多进程连接起来,以便一个进程的输出可以直接作为另一个进程的输入。
在C语言中,`popen()` 函数创建了一个新的进程来执行指定的命令,并与这个进程建立一个管道,通过这个管道,当前进程可以读取或者写入数据到执行命令的子进程的标准输入(stdin)或标准输出(stdout)。
管道在这里不是指进程本身,而是一种通信机制,用于在进程之间传输数据。打开管道后,可以使用像文件一样的操作,比如 fgets()、`fputs()` 来进行读写操作。
popen() 函数返回的是一个 FILE * 类型的文件指针,与一般的文件操作一样,可以使用这个文件指针来进行标准输入输出操作。这就是管道在`popen()`和`pclose()`上下文中的含义。
一个例子是,当使用 popen() 打开管道来读取数据时,实际上是在读取指定命令的标准输出结果;而当打开管道用于写入时,写入的数据实际上是发送到该命令的标准输入。
例如,以下代码会打开一个管道来执行`ls`命令,并读取其输出:

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

int main() {
    char buffer[1024];
    FILE *pipe = popen("ls -la", "r"); // 打开管道,执行"ls -la"命令,"r"表示读取模式

    if (!pipe) {
        perror("popen");
        exit(EXIT_FAILURE);
    }

    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
        printf("%s", buffer); // 从管道读取数据
    }

    pclose(pipe); // 关闭管道

    return EXIT_SUCCESS;
}

在这个例子中,通过popen创建的管道就允许从`ls -la`命令产生的标准输出中读取数据。

system函数说明

system 函数是C语言标准库提供的一个实用函数,它用于在当前程序中执行外部命令。它在 stdlib.h 头文件中声明,并通过创建一个新的进程来运行操作系统的命令解释器(例如,在UNIX系统中为 /bin/sh,在Windows中为 cmd.exe)来执行指定的命令字符串。
函数的原型如下:

int system(const char *command);

参数

- command: 指向一个以 null 结尾的字符串,该字符串包含要由命令解释器执行的命令。传递给 system 函数的字符串就像在命令行中输入的命令。

返回值

- 返回值通常是命令解释器返回的状态代码。但是,它的具体含义可能因实现而异,因此需要参考特定环境下的文档。
- 如果 command 是 NULL,则 system 通常返回一个非零值,如果命令解释器可用的话;如果不可用,则返回 0。
- 如果 command 不是 NULL 而且在尝试启动命令解释器时发生错误,则函数返回 -1。
- 如果 command 成功执行且正常退出,那么通常返回命令的退出状态。在 Unix-like 系统中,这通常是命令退出时的状态码左移8位的结果。

注意事项

1. 当使用 system 函数时,它会让当前程序暂停执行,直到执行的命令完成。
2. 安全性:因为 system 函数调用命令解释器继而执行命令,因此有潜在的安全风险。如果命令字符串来源于不信任的输入,那么可能会受到命令注入攻击。
3. 可移植性:由于 system 函数依赖与系统上可用的命令解释器,所以编写的命令字符串在不同系统之间可能需要不同的修改才能正确运行。
4. 环境影响:调用 system 需要考虑当前工作目录、环境变量等可能影响命令执行的因素。
5. 如果要从执行的命令中获取输出,`system` 函数就不太合适,这时应考虑使用 popen 和 pclose 函数。

示例

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

int main() {
    int return_value;
    return_value = system("pwd && ls"); // 在UNIX系统中打印当前目录并列出文件
    if (return_value == -1) {
        perror("system");
        return 1;
    }
    // 可以对return_value进行进一步解析来获取命令的退出码
    return 0;
}

总之,尽管 system 函数便利,但在使用它时必须考虑代码的安全性和移植性。在可能的情况下,应该寻找更为安全和控制精细的方法来执行外部程序或脚本。

根据C标准,`system()` 函数在以下情况下会返回-1:
1. 如果 system() 函数无法调用 /bin/sh(或者在 Windows 中是 cmd.exe)来执行命令处理器,通常是因为创建新进程(用于执行命令处理器)失败,或者与父进程的连接发生错误。
2. 当 system() 函数遇到系统级别的错误时,比如内存不足或其他资源不足以创建子进程。
如果命令执行失败,但 /bin/sh 被成功调用,例如执行了一个存在错误的命令,`system()` 将不会返回-1。它仍将返回命令处理器的退出码。在这种情况下,返回值通常是一个大于或等于 0 的值,这个值包含了命令处理器返回的状态信息,可以使用宏 WEXITSTATUS 来提取实际的命令退出状态。
要检查 system() 是否返回-1,可以这样做:

int status;

status = system("your-command");
if(status == -1) {
    // system 调用出错
} else {
    if(WIFEXITED(status)) {
        int exit_status = WEXITSTATUS(status);
        // 使用exit_status继续其他操作
    }
}

为了能够使用上面例子中的 WIFEXITED 和 WEXITSTATUS 宏,应该包含 <sys/wait.h> 头文件。注意,在不同的操作系统中处理 system() 函数返回值的细节可能会有所不同。

当使用 system() 函数执行一个命令时,如果命令执行失败,通常会返回一个非零值。但是,`system()` 函数具体返回什么值并不完全取决于是否返回了 -1。
如果尝试使用 system() 执行一个不存在的命令,`system()` 通常会返回一个非零值,表明命令执行失败。这个非零值是命令解释器(例如 shell)的退出状态。在类似于 Unix 的系统上,通常可以通过宏 WEXITSTATUS(status) 来获取子进程的退出状态码。
对于创建已经存在的目录的情况,使用 mkdir 命令时,shell 通常会返回一个非零的退出状态,因为它是一个错误(目录已存在)。然而,这个具体的值是由 mkdir 命令的实现和所运行的 shell 决定的,而不是 -1。
在 Unix 系统中,可以通过 errno 变量来获取系统调用失败具体的错误原因。但是,`system()` 不设置 errno,因为它运行的是一个独立的 shell 进程。
以下是一个使用 system() 的例子,它尝试创建一个已经存在的目录,并检查返回值:

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

int main() {
    int ret;

    // 假设目录/tmp/already_exists已经存在
    ret = system("mkdir /tmp/already_exists");
    if (ret == -1) {
        // system函数自身出错,可能是因为无法调用shell等原因
        perror("system failed");
    } else if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) {
        // Command executed, but returned a non-zero status
        printf("mkdir command failed with exit status: %d\n", WEXITSTATUS(ret));
    } else {
        // Command executed successfully
        printf("mkdir command succeeded\n");
    }

    return 0;
}

在这个例子中,如果 system() 能够成功启动 shell 并执行命令,但是命令执行失败了(比如因为目录已存在),`WEXITSTATUS(ret)` 将提供实际的退出状态。如果 system() 调用失败(无法启动 shell 等),则返回 -1,并且可以使用 perror() 打印出错误信息。
 

相关链接:

linux c语言中执行命令时,命令行包含的文件名中有空格的问题_linux cmd 路径有空格-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/455462.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

雅特力AT32A403开发板评测 03 官方图形化配置工具Work Bench使用

03 雅特力AT32A403开发板评测 官方图形化配置工具Work Bench使用 1. 软硬件平台 AT32A403A Board开发板 MDK-ARM Keil Work Bench 2. AT32 Work Bench 为了方便开发者快速开发芯片&#xff0c;国外大厂的搞了单片机图形化配置工具&#xff0c;生成初始化配置代码&#x…

算法空间复杂度计算

目录 空间复杂度定义 影响空间复杂度的因素 算法在运行过程中临时占用的存储空间讲解 例子 斐波那契数列递归算法的性能分析 二分法&#xff08;递归实现&#xff09;的性能分析 空间复杂度定义 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大…

MyBatis3源码深度解析(十一)MyBatis常用工具类(四)ObjectFactoryProxyFactory

文章目录 前言3.6 ObjectFactory3.7 ProxyFactory3.8 小结 前言 本节研究ObjectFactory和ProxyFactory的基本用法&#xff0c;因为它们在MyBatis的源码中比较常见。这里不深究ObjectFactory和ProxyFactory的源码&#xff0c;而是放到后续章节再展开。 3.6 ObjectFactory Obj…

ES6(三):Iterator、Generator、类的用法、类的继承

一、迭代器Iterator 迭代器是访问数据的一个接口&#xff0c;是用于遍历数据结构的一个指针&#xff0c;迭代器就是遍历器 const items[one,two,three];//创建新的迭代器const ititems[Symbol.iterator]();console.log(it.next()); done&#xff1a;返回false表示遍历继续&a…

04_拖动文件渲染在页面中

新建一个文件夹&#xff0c;跟之前一样&#xff0c;在 Vscode 终端里输入 yarn create electron-app Drag。 在 index.html 添加以下代码&#xff0c;JS 文件夹和 render.js 都是新创建的&#xff1a; 首先&#xff0c;css 文件一般和 html 结合使用&#xff0c;相当于 html 是…

Prometheus 监控系统

目录 概述 Prometheus定义 Prometheus 的特点 Prometheus 的生态组件 Prometheus 的工作模式 Prometheus 的工作流程 Prometheus 的局限性 1.部署 Prometheus 上传prometheus包二级制安装 配置系统启动文件&#xff0c;启动 Prometheust 2.部署 Exporters 上传node…

[Spark SQL]Spark SQL读取Kudu,写入Hive

SparkUnit Function&#xff1a;用于获取Spark Session package com.example.unitlimport org.apache.spark.sql.SparkSessionobject SparkUnit {def getLocal(appName: String): SparkSession {SparkSession.builder().appName(appName).master("local[*]").getO…

探索考古文字场景,基于YOLOv8全系列【n/s/m/l/x】参数模型开发构建文本考古场景下的甲骨文字符图像检测识别系统

甲骨文是一种非常历史悠久的古老文字&#xff0c;在前面我们基本上很少有涉及这块的内容&#xff0c;最近正好在做文字相关的项目开发研究&#xff0c;就想着基于甲骨文的场景来开发对应的检测识别系统&#xff0c;在前文中我们基于YOLOv5、YOLOv7和YOLOv9开发构建了在仿真数据…

c++:类和对象中:拷贝构造和赋值运算符重载详解

c:类和对象 构造函数和析构函数详解 文章目录 c:类和对象构造函数和析构函数详解 前言一、拷贝构造怎么写拷贝构造1.拷贝构造也是构造函数的一种,构造函数没有值.所以拷贝构造也没有返回值**2.拷贝构造只有一个形参,正常这个形参是自定义类型对象的引用.3. 如果我们没有显示写…

Excel判断CD两列在EF两列的列表中是否存在

需求 需要将CD两列的ID和NAME组合起来&#xff0c;查询EF两列的ID和NAME组合起来的列表中是否存在&#xff1f; 比如&#xff0c;判断第二行的“123456ABC”在EF的第二行到第四行中是否存在&#xff0c;若存在则显示Y&#xff0c;不存在则显示N 实现的计算公式 IF(ISNUMBER…

文字弹性跳动CSS3代码

文字弹性跳动CSS3代码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 下载地址 文字弹性跳动CSS3代码

YOLOv9实例分割教程|(一)训练教程

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、创建数据集及数据配置文件 创新一个文件夹存放分割数据集&#xff0c;包含一个images和labels文件夹。标签格式如下所示&#xff1a; 创新数据集…

Linux——文件系统与软硬链接

目录 前言 一、物理上的磁盘 二、磁盘存储的逻辑抽象结构 三、块组内容与工作原理 1.inode 2.Data bolcks 3.inode 位图 4.bolck bitmap 5.GDT 6.Super Block 四、软硬链接 五、软硬链接链接目录 前言 我们之前学习了文件标识符&#xff0c;重定向&#xff0c;缓…

Spring框架----AOP全集

一&#xff1a;AOP概念的引入 首先我们来看一下登录的原理 如上图所示这是一个基本的登录原理图&#xff0c;但是如果我们想要在这个登录之上添加一些新的功能&#xff0c;比如权限校验 那么我们能想到的就有两种方法&#xff1a; ①&#xff1a;通过对源代码的修改实现 ②&a…

Android实现点击获取验证码60秒后重新获取功能

本文实例为大家分享了Android实现点击获取验证码60秒后重新获取的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下 上代码 /*** Created by Xia_焱 on 2017/5/7.*/public class CountDownTimerUtils extends CountDownTimer {private TextView mTextView;/*** param…

Requests教程-17-请求代理设置

上一小节我们学习了requests解决乱码的方法&#xff0c;本小节我们讲解一下requests设置代理的方法。 代理基本原理 代理实际上指的就是代理服务器&#xff0c; 英文叫作proxy server &#xff0c;它的功能是代理网络用户去取得网络信息。形象地说&#xff0c;它是网络信息的中…

Android Studio入门——页面跳转

1.工程目录 2.MainActivity package com.example.demo01;import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCo…

前端开发小技巧 - 【Vue3 + TS】 - 在 TS + Vue3 中使用 Pinia,实现 Pinia 的持久化,优化Pinia(仓库统一管理)

前言 ts 中使用 pinia 和 Vue3 基本一致&#xff0c;唯一的不同点在于&#xff0c;需要根据接口文档给 state 标注类型&#xff0c;也要给 actions 标注类型&#xff1b;以下都是 组合式API 的写法&#xff0c;选项式API 的写法大家可以去官网看看&#xff1b; Pinia&#xff…

Text-to-SQL 工具Vanna进阶|数据库对话机器人的多轮对话

跟数据库对话机器人对话,我可不止一个问题。 可能基于第一句问话,还有第二句、第三句问话。。。第N句对话。所以本文测试了多轮对话功能。 单轮对话的环境搭建参考博客 Text-to-SQL 工具Vanna + MySQL本地部署 | 数据库对话机器人 我的数据是这样 1. 基础配置 import vann…

深入解析Java中锁机制以及底层原理

一、概述 1.1 背景 概念&#xff1a;锁是多线程编程中的机制&#xff0c;用于控制对共享资源的访问。可以防止多个线程同时修改或读取共享资源&#xff0c;从而保证线程安全。 作用&#xff1a;锁用于实现线程间的互斥和协调&#xff0c;确保在多线程环境下对共享资源的访问顺…
最新文章