如何在C语言中调用shell命令

Python010

如何在C语言中调用shell命令,第1张

C语言中调用shell指令,根据调用指令目的,可以区分如下两种情况:

一、需要shell指令执行某一功能,如创建文件夹,或者删除文件夹等,程序中不关注shell指令的输出,那么可以使用system函数

system函数声明于stdlib.h, 功能为调用系统命令,形式为

int system(const char *cmd)

其中cmd为要执行的命令字符串,返回值为执行是否成功的标记。

比如在Linux下要删除当前文件夹下的所有扩展名为a的文件,即*.a, 可以写作

system("rm *.a -f")

二、不仅要执行shell命令,还需要得知运行的打印结果,并在程序中使用。

对于此,有两种方案:

1、用system命令,将输出重定向到一个txt文件中,执行后,再读取txt文件,使用后删除。

比如Linux下获取剩余内存的指令可以写作:

system("free>result.txt")//结果重定向到result.txt中。

FILE *fp = fopen("result.txt", "r")//打开文件。

int r

while(fgetc(fp) != '\n') //忽略第一行。

fscanf(fp, "%*s%*d%*d%d",&r)//读取第四个域的值,即剩余内存值。

printf("剩余内存为%d KB\n",r)//打印结果。

fclose(fp)//关闭文件。

unlink("result.txt")//删除临时文件。

2、使用重定向,需要经过磁盘读写,还要删除文件,相对低效。同时还有可能出现临时文件和已有文件重名,导致误删数据的情况。 所以一般使用更方便快捷的方式,即调用popen。

FILE *popen(const char *cmd, const char *mode)

使用popen的功能和system类似,属于方法1中执行命令和打开文件的一个组合。不过这里用到的文件是隐式的,并不会在系统中真正存在。返回的指针即结果文件指针。 当使用pclose关闭后,文件自动销毁。

方法1中的例子,用popen实现如下:

FILE *fp = popen("free", "r")//执行命令,同时创建管道文件。

int r

while(fgetc(fp) != '\n') //忽略第一行。

fscanf(fp, "%*s%*d%*d%d",&r)//读取第四个域的值,即剩余内存值。

printf("剩余内存为%d KB\n",r)//打印结果。

pclose(fp)//关闭并销毁管道文件。

三、注意事项:

虽然调用shell命令有时可以大大减少代码量,甚至有千行代码不如一句shell的说法,不过调用shell命令还是有局限性的:

1、使用shell命令会调用系统资源,效率偏低;

2、不同平台的shell指令不同,导致可移植性下降;

3、调用shell命令时会复制当前进程(fork),如果当前进程的资源占有比较大,会导致瞬间资源占用极大,甚至可能出现失败。

所以,在编码时,除非是测试性的代码,否则在正式代码中不建议使用shell。

题主可以使用 exec 系列函数。这系列函数定义在 unistd.h 头文件中,所以使用前请包含这个头文件。这系列函数共有五个,

execl, execlp, execv, execvp, execle

其中常用的是前四个。前四个函数的原型为:

int execl(const char *path, const char *arg, ...)

int execlp(const char *file, const char *arg, ...)

int execv(const char *path, char *const argv[])

int execvp(const char *file, char *const argv[])

这四个函数的主要差别就在于参数的类型和用不用输入命令的绝对路径上。

以路径形式来分,凡是函数名中带 p 的(execlp,execvp)都只需要提供命令的名,函数会自动在当前的环境变量 $PATH 中查找命令的路径。而不带 p 的(execl,execv)必须要提供命令的绝对路径,否则函数会找不到这个命令的位置。这里以 execl 和 execlp 为例,以下运行

ls -l

命令的代码:

#include <stdio.h>

#include <unistd.h>

int main() {

    // exec 系列函数出错时会返回 -1,平常返回 0,所以可以

    // 据此来打印错误信息

    // 第一个 ls 是命令的名称,execlp 函数会自动在 $PATH

    // 中寻找这个命令。

    // 后面一个 ls 是要在 shell 中输入的第一个参数

    //(也就是命令名称本身)

    // 使用 NULL 作为参数结尾标记是 exec 系列函数的要求。

    if (execlp("ls", "ls", "-l", NULL) == -1)

        perror("Error Executing Command.\n")

    return 0

    

}

在 shell 中运行这个 C 程序会输出

和你直接在 shell 中写 ls -l 的效果是一样的。然而,如果你使用不带 p 的 execl, 那么这样写就会报错。

#include <stdio.h>

#include <unistd.h>

int main() {

    // execl 只接受命令的绝对路径,所以必须输入完整的

    // 路径 /bin/ls,即

    // if (execl("/bin/ls", "ls", "-l", NULL) == -1)

    if (execl("ls", "ls", "-l", NULL) == -1)

        perror("Error Executing Command.\n")

    return 0

    

}

输出结果为:

以参数类型来分,凡是函数名中带 l 的(execl,execlp)都需要把系统命令的参数全部传递给函数,而凡是函数名中带 v 的(execv,execvp)都需要把系统命令的参数统一放在一个数组里,然后把这个数组传递给函数。

比如刚才这个

#include <stdio.h>

#include <unistd.h>

int main() {

    if (execlp("ls", "ls", "-l", NULL) == -1)

        perror("Error Executing Command.\n")

    return 0

    

}

如果改用 execvp 来写的话就是

#include <stdio.h>

#include <unistd.h>

int main() {

    // 这个字符串数组存有所有参数(包括命令名本身和

    // 最后的 NULL)

    char * argv[] = {"ls", "-l", NULL}

    

    // 这里只需将命令名称和参数数组传递给 execvp 函数即可,

    // 无需将参数一个个传递。同样函数会自动在 $PATH 

    // 中查找命令

    if (execvp("ls", argv) == -1)

        perror("Error Executing Command.\n")

    return 0

    

}

运行结果同样和直接写 ls -l 的效果相同。

execv 和 execvp 的区别也在于是否必须输入绝对路径,就不赘述了。

要注意的一点是,如果执行成功,exec 系列函数开启的新进程会完全代替当前的进程,也就是说当前进程会消失。所以一般会将 exec 和 fork 连用,先 fork 出一个子进程,然后在这个子进程中使用 exec 运行别的程序,防止父进程被 exec 覆盖。比如刚才的代码稍微改一下

#include <stdio.h>

#include <unistd.h>

int main() {

    char * argv[] = {"ls", "-l", NULL}

    if (execvp("ls", argv) == -1)

        perror("Error Executing Command.\n")

        

    // 加入一个 printf 语句

    printf("Main process is still running.\n")

    return 0

    

}

运行后并不会出现 Main process is still running 这句话,因为 exec 后 main 函数执行产生的进程已经被 ls 命令产生的进程完全覆盖了,所以 exec 函数以下的语句是完全不会执行的。这时就可以使用 fork 来新建一个子进程,在子进程中执行 exec 函数。

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

int main() {

    

    int r

    

    // fork() 大于零,父进程

    if ((r = fork()) > 0) {

        int status

        if (wait(&status) != -1) {

            

            // 等待子进程退出

            if(WIFEXITED(status)) {

                printf("Main process is still running.\n")

                return 0

            }

        }

    

    // fork () 等于零,子进程。子进程中运行 exec    

    } else if (r == 0) {

    

        char * argv[] = {"ls", "-l", NULL}

        if (execvp("ls", argv) == -1)

            perror("Error Executing Command.\n")

        return 0

    

    // fork() 小于零,出错

    } else {

        perror("Fork")

    }

    

    return 0

    

}

这样运行结果就变成了

Main process is still running 这句话就会被输出到屏幕上。