当我们为Linux编写程序时,我们必须考虑到程序会运行在多任务环境下。这就意味着多个程序会同时运行,并且共享机器资源,例如内存,磁盘空间以及CPU周期。也许在同一时刻会一个程序多个实例在运行。这时最为重要的就是这些程序之间不会相互影响,彼此清楚其周边环境,同时也要正确的运行以避免冲突,例如与另一个程序同时试着写入相同的文件等。
在这一章,我们将会讨论程序执行的环境,他们如何使用环境来得到有关操作环境的信息,以及这些程序用户如何改变其行为。具体的说,我们会讨论下面内容:
向程序传递参数
环境变量
查看时间信息
临时文件
得到用户以及主机的信息
记录以及配置日志消息
发现系统的限制
程序参数
当一个使用C语言编写的Linux或是Unix程序运行时,他由main函数开始执行。对于这些程序来说,main函数声明为
int main(int argc, char *argv[])
在这里argc是程序参数的个数,而argv是一个代表参数本身的字符串数组。
有时我们也会看到Linux的C程序简单的声明为
main()
这仍然可以正常工作,因为返回类型默认为int型,而在函数不需要的常规参数并不需要声明。argc与argv仍然存在,但是如果我们不进行声明,我们就没有办法使用。
当操作系统启动一个新程序时,argc与argv就会被设置,并传递给main。这些参数通常是由另一个程序来提供的,通常是请求操作系统执行一个新程序的shell。shell会读取所给定的命令行参数,将其分为单个的单词,并且用他们来设置argv数组。记住,在argc与argv被设置之前,Linux shell通常会对文件名参数执行通配符扩展,而MS-DOS shell会期望程序接受带有通配符的参数,并且执行他们自己的通配符扩展。
例如,如果我们为shell提供下面的命令:
$ myprog left right ‘and center’
myprog程序就会用下列参数来启动main函数:
argc: 4
argv: {“myprog”, “left”, “right”, “and center”}
注意,参数个数包括程序本身的名字,并且argv数组包含程序名字作为其第一个元素,argv[0]。因为我们在shell命令中使用了引号,第四个参数包含一个带有空格的字符串。
如果我们使用过ISO/ANSI C编写过程序,我们就会对这些内容感到熟悉。main参数相对应于shell脚本中的位置参数,$0,$1,依次类推。然而ISO/ANSI C说明main必须返回int,X/Open的描述包含上面给出的显示声明。
命令行参数对于向程序传递信息是十分有用的。例如,我们可以在一个数据库程序中使用命令行参数来传递我们希望使用的数据库名,这样就可以允许我们在多个数据库上使用相同的程序。许多实用程序也使用命令行参数来改变他们的行为或是设置选项。我们可以使用带有短划线的命令行参数来设置这些所谓的标记,或是开关。例如,sort程序带有一个开关来反转通常的排列顺序。
$ sort -r file
命令行参数十分常见,而且恰当的使用对于使用我们程序的人来说确实是一个极大的帮助。过去,所有的实用程序都实现了他们各自的命令选项方法,这就造成混乱。例如,看一下下面这些命令读取参数的方式:
$ tar cvfB /tmp/file.tar 1024
$ dd if=/dev/fd0 of=/tmp/file.dd bs=18k
$ ls -lstr
$ ls -l -s -t -r
所有的命令行参数都应以一个短划线开始,并且由一个字母或是数字组成。没有更多参数的选项可以在短划线后组织在一起。所以上面显示的两个ls命令都遵守了这个约定。如果选项需要一个单独的参数,那么可以在其后跟一个值。dd命令的例子并没有遵守这个规则,他使用了多个字符选项,并且没有以短划线开始(if=/dev/fdo);而tar命令将其选项与值进行完全的分离。
另外一些程序的缺点就是使用选项+x来执行与-x相反的功能。
正如我们所看到的,如果不使用特殊的格式处理,记住所有这些程序选项的顺序和意义是相当困难的。通常,唯一的资源就是使用一个-h(help)选项,或者是man页。正如我们在后面将会看到,getopt为这些问题提供了一个灵巧的解决办法。但是现在,让我们看一下所传递的程序参数的处理。
试验--程序参数
这里是一个程序,args.c,来检测他自己的参数:
#include <stdio.h>
int main(int argc, char *argv[])
{
int arg;
for(arg = 0; arg < argc; arg++) {
if(argv[arg][0] == ‘-’)
printf(“option: %s/n”, argv[arg]+1);
else
printf(“argument %d: %s/n”, arg, argv[arg]);
}
exit(0);
}
当我们运行这个程序时,他只是打印出其参数与检测选项。其目的在程序读取一个字符串参数以及一个由-f选项引入的可选的文件名参数。同时也定义了其他的选项。
$ ./args -i -lr ‘hi there’ -f fred.c
argument 0: args
option: i
option: lr
argument 3: hi there
option: f
argument 5: fred.c
工作原理
程序只是简单的使用程序计数argc设置一个循环来检测所有的程序参数。他通过查找一个初始短划线来检测选项。
在这个例子中,如果我们的设计只是-l与-r是可用的,那么也许我们就会失去将-lr本应看作与-l -r相同的事实。
X/Open描述为命令行选项定义了一个标准用法,同时在C程序中为提供命令行开关提供定义了一个标准程序接口:getopt函数。
getopt
为了帮助我们遵守这些规则,Linux为我们提供了getopt函数,他支持带值与不带值的选项用法,并且使用简单。
#include <unistd.h>
int getopt(int argc, char *const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
getopt函数读取传递给程序的main函数的argc与argv参数以及一个选项描述符字符串,这个字符串通知getopt为这个程序定义了哪些选项,以及这些选项是否有相关联的值。optstring只是一个简单的字符列表,每一个代表一个字符选项。如果一个字符后跟有一个冒号,那么他表明这个选项有一个与其相关联的值,应读取作为下一个参数。bash中的getopts命令有着相类似的功能。
例如,下面的调用可以用来处理我们前面的例子:
getopt(argc, argv, “if:lr”);
他允许简单的选项-i,-l,-r,以及后跟文件名参数的-f选项。使用相同的参数但是以不同的顺序调用这个命令会改变其行为。当我们遇到后面的示例代码时可以进行尝试。
getopt的返回结果是argv数组中下一个选项字符(如果有)。我们重复调用getopt来依次得到每个选项。他有下面的行为:
如果选项带有一个参数值,那么这个值是由外部变量optarg来指向的。
当没有选项要进行处理时,getopt会返回-1。一个特殊的参数,--,会使得getopt停止搜索。
如果有一个不可识别的选项,他会返回?,并且选项会存储在外部变量optopt中。
如果一个选项需要一个参数值(例如我们例子中的-f),但是却没有参数值时,getopt会返回。
外部变量optind设置为要处理的下一个参数的索引。getopt会使用这个变量来记录他处理到了哪里。程序通常并不需要这个变量。当所有的选项参数处理完毕时,optind会指示在argv数组末尾的何处可以在找到其余的参数。
一些版本的getopt会在遇到第一个非选项参数时停止,返回-1并且设置optind。其他的一些版本,例如Linux所提供的,可以处理程序参数中的所有选项。注意,在这个例子中,getopt会重改定argv数据,这样所有的非选项参数就可以组织在一起,由argv[optind]处开始。对于GNU版本的getopt,其行为是由环境变量POSIXLY_CORRECT来控制的。如果设置,getopt就会在第一个非选项参数处停止。另外,一些getopt实现会为不可知的选项打印错误信息。注意,POSIX的描述说明是,如果opterr变量为非零,getopt就会向stderr打印一个错误消息。
试验--getopt
现在我们在我们的例子中使用getopt,并且将这个新程序称之为argopt.c:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int opt;
while((opt = getopt(argc, argv, “if:lr”)) != -1) {
switch(opt) {
case ‘i’:
case ‘l’:
case ‘r’:
printf(“option: %c/n”, opt);
break;
case ‘f’:
printf(“filename: %s/n”, optarg);
break;
case ‘:’:
printf(“option needs a value/n”);
break;
case ‘?’:
printf(“unknown option: %c/n”, optopt);
break;
}
}
for(; optind < argc; optind++)
printf(“argument: %s/n”, argv[optind]);
exit(0);
}
现在,当我们运行这个程序时,我们发现所有的命令行参数被自动处理了:
$ ./argopt -i -lr ‘hi there’ -f fred.c -q
option: i
option: l
option: r
filename: fred.c
argopt: invalid option-—q
unknown option: q
argument: hi there
工作原理
程序会重复调用getopt来处理选项参数,直到没有选项需要处理为止,此时getopt会返回-1。这个动作会在每一个选项上执行,包括处理不可知的选项以有没有选项参数值的情况。依据我们的getopt版本,我们得到的输出与上面的与许不同,尤其是错误信息,但是意义是清楚的。
一旦所有的选项都被处理了,程序就会简单的打印出剩余的参数,这与前面的例子相同,但是却由optind开始的。
getopt_long
许多Linux程序同时会接受比我们在上面的例子中所使用的单字符选项更有意义的参数。GNU C库包含一个被称之为getopt_long的getopt版本,他可以接受由双短划线引入的所谓长参数。
我们使用getopt_long来创建我们上面例子程序的一个新版本,这样他就可以使用与我们的选项等同的长参数来调用了。
事实上,新的长选项与原始的单字符选项可以混合。只要他们存在不同,长选项也可以简写。带有参数的长选项可以--option=value的格式给出,如下所示:
$ ./longopt —init –l —file=fred.c ‘hi there’
option: i
option: l
filename: fred.c
argument: hi there
新程序longopt.c如下所示:
#include <stdio.h>
#include <unistd.h>
#define _GNU_SOURCE
#include <getopt.h>
int main(int argc, char *argv[])
{
int opt;
struct option longopts[] = {
{“initialize”, 0, NULL, ‘i’},
{“file”, 1, NULL, ‘f’},
{“list”, 0, NULL, ‘l’},
{“restart”, 0, NULL, ‘r’},
{0,0,0,0}};
while((opt = getopt_long(argc, argv, “if:lr”, longopts, NULL)) != -1) {
switch(opt) {
case ‘i’:
case ‘l’:
case ‘r’:
printf(“option: %c/n”, opt);
break;
case ‘f’:
printf(“filename: %s/n”, optarg);
break;
case ‘:’:
printf(“option needs a value/n”);
break;
case ‘?’:
printf(“unknown option: %c/n”, optopt);
break;
}
}
for(; optind < argc; optind++)
printf(“argument: %s/n”, argv[optind]);
exit(0);
}
工作原理
与getopt相比,getopt_long带有两个额外的参数。第一个为一个结构数组,描述了长选项并且通知getopt_long如何来处理。第二个额外参数是一个指向可以用作长选项版本的optind变量的指针;对于每一个识别的长选项,他在长选项数组中的索引可以写入这个变量。在我们的例子中,我们并不需要这个信息,所以我们使用NULL作为第二个额外参数。
长选项数组是由一些type struct option的结构组成的,每一个描述了长选项的行为。这个数组必须以一个包含零的结构结束。
长选项结构定义在getopt.h中,而且必须使用_GNU_SOURCE常包含,用来允许getopt_long功能:
struct option {
const char *name;
int has_arg;
int *flag;
int val;
};
结构的成员如下:
name 长选项的名字。只要他们的简写不与其他的选项冲突就可以被接受。
has_arg 这个选项是否带有参数。如果不需要参数,将其设置为0;如果必须有一个参数值,将其设置为1;如果有一个可选参数,将其设置为2。
flag 将其设置为NULL,使得getopt_long在查找到这个选项时返回val中定的值。否则,getopt_long会返回0,并且将val的值写入由flag所指向的变量。
val getopt_long为这个选项返回的值。
要了解其他与getopt的GNU扩展相关联的选项以及相关的功能,可以查看getopt的手册页。