上一节介绍了 linux 中的文件类型,并在文章最后使用 C语言编写了程序,该程序能够接受一个文件名参数,并打印出该文件的类型。不知道大家如何,反正我当初学编程时,发现(编译后的)可执行程序居然也能像(编写代码阶段的)函数一样接收参数,觉得太神奇了。
小编刚学习 C语言时,是在 windows 中学习的,编译出的程序都是双击执行,从来没想过编译后的可执行程序还能接收参数。
可执行程序怎样接收参数的呢?
事实上,不仅仅是上一节的C语言程序能够接受参数,linux 中的大部分 shell 命令都是可以接收参数的,例如 ls 命令可以接收 -l 参数,输出更加详细的文件信息:
执行删除命令 rm 也需要指定文件名:
ls 和 rm 本质上也是 linux 中的可执行程序,linux 中的大部分程序都是由 C语言编写的。C语言程序总是有个入口函数(常常是 main 函数),入口函数的原型如下:
其中 argc 是命令参数的数目,argv 则是指向参数的各个指针构成的数组。在 shell 中输入命令(其实就是可执行程序)后,shell 会调用 exec 函数族执行该命令。输入 man 命令查询 exec 函数族的手册:
容易看出,exec 函数族在创建新进程执行命令时,允许传入若干参数给命令。这就明白了,shell 也是一个进程,它会记录用户输入的命令和命令参数,在调用 exec 函数族执行命令时,把记录的参数传递给命令。
C语言模拟 shell 传递参数给可执行程序
知道了 linux 中可执行程序接收参数的原理后,编写程序模拟 shell 传递参数就不难了。我们先编写一个能够接受参数的C语言程序:
程序很简单,就是将接收到的参数打印出来。编译并执行之,得到如下结果:
因为规定所有有效参数之后 argv 指向 NULL,所以遍历 C语言程序接收到的所有参数时,上面的 for 循环也可以写成:for(i=0; NULL!=argv[i]; i++)。
现在再编写一个程序,在这个程序中,我们将使用 exec 函数族模拟 shell 执行由 t.c 编译而来的可执行程序 a.out,并向其传送指定的参数。
代码很简单,将“hello”和“embedTime”两个参数填入 argv 里,再调用 execvp 函数模拟 shell 执行 a.out 程序,编译执行:
模拟 shell 的 sim.out 程序的确成功把上面两个参数传递给 a.out 了,但是 sim.out 程序最后的 “sim shell exit.”信息却没有输出。这是因为 sim.out 进程被 a.out 进程替代了。
编写完美模拟 shell 的C语言程序
这样模拟 shell 并不完美,总不至于为了执行一个进程,shell 都得退出吧?还记得第 11 节介绍的多进程 C语言程序编写方法吗?为了完美模拟 shell,可以 fork 出一个子进程,在子进程中执行 a.out ,请看如下代码:
现在再编译执行,发现我们不仅成功模拟了 shell 执行 a.out ,而且 a.out 执行时,sim.out 也没有被替换,终于较为完美的模拟了 shell 传递参数给可执行程序。