14

linux下的C开发14,可执行程序如何传递参数?模拟shell执行命令

 4 years ago
source link: https://blog.popkx.com/linux%E4%B8%8B%E7%9A%84c%E5%BC%80%E5%8F%9114-%E5%8F%AF%E6%89%A7%E8%A1%8C%E7%A8%8B%E5%BA%8F%E5%A6%82%E4%BD%95%E4%BC%A0%E9%80%92%E5%8F%82%E6%95%B0-%E6%A8%A1%E6%8B%9Fshell%E6%89%A7/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

linux下的C开发14,可执行程序如何传递参数?模拟shell执行命令

发表于 2019-01-06 13:01:58   |   已被 访问: 1,088 次   |   分类于:   Linux笔记   |   2 条评论

上一节介绍了 linux 中的文件类型,并在文章最后使用 C语言编写了程序,该程序能够接受一个文件名参数,并打印出该文件的类型。不知道大家如何,反正我当初学编程时,发现(编译后的)可执行程序居然也能像(编写代码阶段的)函数一样接收参数,觉得太神奇了。

3dcdb3f8f5f0b900ac7be4991d65fe19.png

小编刚学习 C语言时,是在 windows 中学习的,编译出的程序都是双击执行,从来没想过编译后的可执行程序还能接收参数。

可执行程序怎样接收参数的呢?

事实上,不仅仅是上一节的C语言程序能够接受参数,linux 中的大部分 shell 命令都是可以接收参数的,例如 ls 命令可以接收 -l 参数,输出更加详细的文件信息:

# ls -l
total 24
-rwxr-xr-x 1 root root 13205 Dec 22 18:26 a.out
-rw-r--r-- 1 root root   427 Dec 21 21:01 main.c
-rw-r--r-- 1 root root  1653 Dec 22 18:26 test.c
fd34f41a2f462d17bf8d58135cef986a.png

执行删除命令 rm 也需要指定文件名:
# rm a.out

ls 和 rm 本质上也是 linux 中的可执行程序,linux 中的大部分程序都是由 C语言编写的。C语言程序总是有个入口函数(常常是 main 函数),入口函数的原型如下:

int main(int argc, char* argv[]);

其中 argc 是命令参数的数目,argv 则是指向参数的各个指针构成的数组。在 shell 中输入命令(其实就是可执行程序)后,shell 会调用 exec 函数族执行该命令。输入 man 命令查询 exec 函数族的手册:

6fc38b3f3705a08a08980628e4bc3f46.png

容易看出,exec 函数族在创建新进程执行命令时,允许传入若干参数给命令。

这就明白了,shell 也是一个进程,它会记录用户输入的命令和命令参数,在调用 exec 函数族执行命令时,把记录的参数传递给命令。

C语言模拟 shell 传递参数给可执行程序

知道了 linux 中可执行程序接收参数的原理后,编写程序模拟 shell 传递参数就不难了。我们先编写一个能够接受参数的C语言程序:

#include <stdio.h>
int main(int argc, char* argv[])
{
    int i = 0;
    printf("\n");
    for(i=0; i<argc; i++)
      printf("\t%s\n", argv[i]);
    printf("\n");
    return 0;
}
d988d8b426585212a66534ae8fc31a4d.png

程序很简单,就是将接收到的参数打印出来。编译并执行之,得到如下结果:
# gcc t.c
# ./a.out 

        ./a.out

# ./a.out hello embedTime

        ./a.out
        hello
        embedTime

#
4d055cb6e9876f9db1d5b7e751e5a547.png

因为规定所有有效参数之后 argv 指向 NULL,所以遍历 C语言程序接收到的所有参数时,上面的 for 循环也可以写成:for(i=0; NULL!=argv[i]; i++)。

现在再编写一个程序,在这个程序中,我们将使用 exec 函数族模拟 shell 执行由 t.c 编译而来的可执行程序 a.out,并向其传送指定的参数。

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

int main()
{
    char* filename = "./a.out";
    printf("now run %s\n", filename);
    char *argv[] = {"hello","embedTime",NULL};
    int ret = execvp(filename, argv);   
    if(ret<0)
        printf("ret: %d %s\n", ret, strerror( errno ));
    printf("sim shell exit.\n\n");
    return 0;
}
565b7f7d14ef7e8122ee17d081a29901.png

代码很简单,将“hello”和“embedTime”两个参数填入 argv 里,再调用 execvp 函数模拟 shell 执行 a.out 程序,编译执行:

223fde93b6412499a09b2ee25c7f4158.png

模拟 shell 的 sim.out 程序的确成功把上面两个参数传递给 a.out 了,但是 sim.out 程序最后的 “sim shell exit.”信息却没有输出。这是因为 sim.out 进程被 a.out 进程替代了。

编写完美模拟 shell 的C语言程序

这样模拟 shell 并不完美,总不至于为了执行一个进程,shell 都得退出吧?还记得第 11 节介绍的多进程 C语言程序编写方法吗?为了完美模拟 shell,可以 fork 出一个子进程,在子进程中执行 a.out ,请看如下代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

int main()
{
    char* filename = "./a.out";
    pid_t pid = fork();

    if(0==pid){
        printf("now run %s\n", filename);

        char *argv[] = {"hello","embedTime",NULL};

        int ret = execvp(filename, argv);   
        if(ret<0)
            printf("ret: %d %s\n", ret, strerror( errno ));
    }else{
        printf("\nsim shell running...\n");

        int status;
        wait(&status);

        printf("sim shell exit.\n\n");
    }
    return 0;
}
caeabf83998d6a8974bf1fb31b6d0608.png

现在再编译执行,发现我们不仅成功模拟了 shell 执行 a.out ,而且 a.out 执行时,sim.out 也没有被替换,终于较为完美的模拟了 shell 传递参数给可执行程序。

阅读更多:   Linux笔记


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK