Linux环境
- 导语
- 程序参数
- getopt
- getopt_long
- 环境变量
- getenv/putenv
- environ
- 时间和日期
- time
- difftime和gmtime
- ctime&asctime
- strftime/strptime
- 临时文件
- tmpnam
- tmpfile
- 获取信息
- 用户
- 主机
- 日志
- 资源和限制
- 总结
- 参考文献
导语
任何程序都是在一定的环境下运行的,通常这些环境除了有路径之外,还有权限、外部变量、系统变量等,这些都可以在程序中被调用
程序参数
在Linux中运行C语言程序时,程序是从main函数开始运行的,main函数有两个参数,参数个数和参数数组。一般来说是用shell来启动一个C程序,这个时候就可以通过给shell追加参数来实现参数对C语言的输入,需要注意的是程序名本身是参数0
除此之外,在使用命令行时还需要遵守一定的书写规范,所有命令行开关应以一个短横线开头,然后跟着单个字母或数字,不带后续参数的选项可以归并,如果某选项需要值,则该值应作为独立参数紧跟选项
getopt
getopt函数原型如下
int getopt(int argc, char *const argv[], const char *optstring);
参数分别为main的argc和argv,optstring是字符列表,每个字符代表一个单字符选项,如果字符紧跟冒号,则代表该选项有值,下一个在参数就是其值
与getopt密切相关的还有外部变量optarg、optind、opterr、optopt
getopt的返回值是argv数组的下一个选项字符,可以认为通过getopt对选项进行单次遍历,遍历的具体情况如下
getopt通过optind来记录已经访问的记录,opterr用来判断是否需要向stderr输出,下面是书上的一个例子和运行结果
#include <stdio.h>
#include <unistd.h>
#include <stdlib.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 need a value\n");
break;
case '?':
printf("unknown option: %c\n",optopt);
break;
}
}
for(; optind<argc; optind++)
printf("argument: %s\n",argv[optind]);
return 0;
}
可以看到所有参数都被切割开来并分别处理
getopt_long
getopt_long的函数比getopt多几个参数,函数原型如下
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int flags);
longopts是一个指向 option 结构数组的指针,用于描述长选项。每个 option 结构包含长选项的名称、是否有附加参数、选项的简短描述(用于错误消息)以及一个标志,指示该选项是否被找到
longindex:如果非 NULL,则指向一个变量,该变量将设置为找到的长选项在 longopts 数组中的索引
flags:控制 getopt_long 行为的标志。最常用的标志是 no_argument(0)、required_argument(1)和 optional_argument(2),它们用于指定长选项是否需要附加参数
option中变量的定义如下
环境变量
环境变量可以用来控制shell脚本和程序行为,每个用户都有一个自己的环境变量,用来标注用户的工作目录,环境的格式为“名字=值”,一般来说可以通过getenv和putenv来对环境变量进行修改
getenv/putenv
getenv和putenv的函数原型如下
char *getenv(const char *name)
int putenv(cons char *string)
书上给出的例子和运行结果如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char * argv[])
{
char *var,*value;
if(argc==1||argc>3)//判断错误
{
fprintf(stderr,"usage: environ var [value]\n");
exit(1);
}
var =argv[1];//拿要找的环境名称
value=getenv(var);//拿到具体环境值
if(value)
printf("Variable %s has value %s\n",var,value);
else
printf("Variable %s has no value\n",var);
if(argc==3)//如果是赋值
{
char *string;
value=argv[2];
string=malloc(strlen(var)+strlen(value)+2);
if(!string)
{
fprintf(stderr,"out of memory\n");
exit(1);
}
strcpy(string,var);
strcat(string,"=");
strcat(string,value);
printf("putenv: %s\n",string);
if(putenv(string)!=0)
{
fprintf(stderr,"putenv failed\n");
free(string);
exit(1);
}
value =getenv(var);
if(value)
printf("New value of %s is %s\n",var,value);
else
printf("New value of %s is null\n",var);
exit(0);
}
return 0;
}
可以看到对环境的修改作用域仅限程序,这是因为变量的值不会从子进程(运行程序)传到父进程(shell)
environ
程序的环境实际上是一个字符数组,程序可以通过environ变量直接访问数组,书上给出的例子和结果如下,可以看到,这个程序遍历的所有的环境变量,并将他们输出
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern char **environ;
int main(int argc,char * argv[])
{
char **env=environ;
while(*env)
{
printf("%s\n",*env);
env++;
}
return 0;
}
时间和日期
时间通过一个预定义的time_t来处理,它是一个长整型,所有UNIX系统都以1970.1.1.0为起点
time
可以通过调用time函数得到底层的值,函数原型如下
time_t time(time_t *tloc);//返回值写入tloc
书上给出的一个简单的例子和运行结果如下,每10s打印一次底层时间值
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int x;
time_t tm;
for (int i=1;i<=5;i++)
{
tm=time(0);
printf("The time is %ld\n",tm);
sleep(1);
}
return 0;
}
difftime和gmtime
difftime用来计算两个time_t值之间的秒数并返回,函数原型如下
double difftime(time_t time1, time_t time2);
//计算时间差并将差值作为浮点数返回
Linux在进行底层运算时,通常是使用绝对的秒数,但是绝对的秒数通常很大,并不方便阅读,因此需要将时间转换成用户容易理解的形式,gtime函数就是用来实现这个功能的
gtime的函数如下
sturct tm *gmtime(const time_t *timeval);
struct tm的定义如下
下面是书上给出的一个简单的例子以及运行结果
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <math.h>
#include <time.h>
int main()
{
struct tm *tm_ptr;
time_t ttm;
(void)time(&ttm);
tm_ptr=gmtime(&ttm);//获得绝对秒数并将结果作为tm返回
printf("second:%ld\n",ttm);
printf("gmtime: date:%02d/%02d/%02d\n",tm_ptr->tm_year,tm_ptr->tm_mon+1,tm_ptr->tm_mday);//访问tm的信息
printf("time: %02d:%02d:%02d\n",tm_ptr->tm_hour,tm_ptr->tm_min,tm_ptr->tm_sec);//同上
return 0;
}
还有一些别的方便显示的函数,例如当所处时区不在GMT或者使用夏令时,显示的时间可能就不对,这个时候可以用localtime、mktime等函数实现符合时区的正确的显示
ctime&asctime
为了获得更好的显示,可以使用ctime和asctime,两者的函数原型如下
char *asctime(const struct tm *timeptr);
char *ctime(const time_t *timeval);
两者的参数不同,一个是tm格式,另一个是time_t格式,但是返回的都是一个特定格式的时间字符串,书上给出的例子和运行结果如下
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <math.h>
#include <time.h>
int main()
{
time_t v;
(void)time(&v);//给时间赋值
printf("The date is: %s\n", ctime(&v));//按照指定格式输出
return 0;
}
strftime/strptime
为了方便实现对时间的格式有更多的控制,Linux还包含了strftime和strptime两个函数,函数原型如下
size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr);
//存储目标,长度,格式串,时间变量来源
char *strptime(const char *buf, const char *format, struct tm *timeptr);
//存储目标,格式串,时间变量来源
它们有点类似一个针对时间的sprintf函数,第一个参数都是存储的目标,最后一个都是来源,中间都存在一个规定格式串,转换符较多,这里不展示,以下是书上的一个例子和执行的结果
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <math.h>
#include <time.h>
int main()
{
struct tm *tm_ptr, timestruct;
time_t ttime;
char buf[256],*result;
(void)time(&ttime);//获得时间
tm_ptr=localtime(&ttime);//转成符合当地的时间格式
strftime(buf,256,"%A %d %B, %I:%M %p",tm_ptr);//把时间格式写入buf
printf("strftime gives: %s\n",buf);
strcpy(buf,"Mon 12 June 2003, 12:33 will do fine");//赋值
printf("calling strptime with: %s\n", buf);
tm_ptr=×truct;
result=strptime(buf,"%a %d %b %Y, %R",tm_ptr);
printf("strptime consumed up to: %s\n",result );
printf("strptime gives:\n");
printf("date: %02d/%02d/%02d\n",tm_ptr->tm_year%100,tm_ptr->tm_mon+1);
return 0;
}
调用time和localtime得到当前本地时间,然后使用合适的format转换时间,之后将转换完的时间串进行输出
临时文件
程序在运行的时候经常产生临时文件,这些临时文件可能保存中间结果,或者是备份,临时文件的用法很常见,但是有一个隐藏的缺点,就是临时文件时在使用的时候是唯一的,即程序和文件之间是一一对应的关系,如果没有这样的对应关系,因为Linux允许文件重名,就可能导致程序相互干扰
tmpnam
tmpnam可以生成一个唯一的文件名,函数原型如下
char *tmpnam(char *s);
//返回一个不与任何文件同名的文件名,即使s不空也会被刷新
tmpnam的后续调用会覆盖返回值的静态存储区,如果多次调用必须传入字符串参数,每次调用都会返回不一样的文件名。
tmpfile
如果临时文件需要立刻使用,可以使用tmpfile在命名的同时打开,以避免另一个程序创建出一个与tmpnam返回的文件名同名的文件,函数原型如下
FILE *tmpfile(void)
//创建一个文件流指针,指向一个唯一的临时文件,rw,文件的所有引用关闭则自动删除,出错则返回NULL,设置errno
书上给出的例子和运行结果如下
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <math.h>
#include <time.h>
int main()
{
char tmpname[L_tmpnam],*filename;
FILE *tmpfp;
filename=tmpnam(tmpname);//创一个临时文件名
printf("Temporary file name is: %s\n",filename);
tmpfp=tmpfile();//也是创一个临时文件,但是是流的方式
if(tmpfp)
printf("Opened a temporary file OK\n");
else
perror("tmpfile");
return 0;
}
程序为临时文件生成一个唯一的文件名,tmpfile调用同时创建和打开一个临时文件,需要注意的是在使用的时候会弹出警告,这也提醒我们之后最好使用mkstemp而不是tmpnam
Linux还有其他的生成临时文件名的方式,mktemp和mkstemp,用法和tmpnam类似,具体不再赘述,尽量使用tmpfile和mkstemp而不是tmpnam和mktemp
获取信息
用户
用户有一个唯一用户标识符UID,每个程序也会与对应的UID相关联,UID有其自己的类型uid_t,通常为一个小整数,可以通过一些基本的函数来获得,如getuid、getlogin、getpwuid、getpwnam等,以下是函数原型
uid_t getuid(void);//返回程序关联的uid
char *getlogin(void);//与当前用户关联的用户名
struct passwd *getpwuid(uid_t uid);//拿密码
struct passwd *getpwnam(const char *name);//同上
这里给出struct passwd的成员
下面是书上给出的一个例子和运行结果
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
int main()
{
uid_t uid;
gid_t gid;
struct passwd*pw;
uid=getuid();
gid=getgid();
printf("User is %s \n",getlogin());
printf("User IDs: uid=%d, gid=%d",uid, gid);
pw=getpwuid(uid);
printf("UID passwd entry :\n name=%s, uid=%d, gid=%d, home=%s, shell=%s\n",pw->pw_name,pw->pw_uid,
pw->pw_gid,pw->pw_dir,pw->pw_shell);
return 0;
}
可以看到该程序访问到了用户涉及到的信息,管理员权限的信息需要加上sudo
除了这几个之外还有一些别的函数,例如getwent、geteuid等
主机
与用户信息一样,主机的信息也同样可以获得,如果系统安装了网安组件,可以通过gethostname获取其网络名
int gethostname(char *name, size_t namelen)//把机器的网络名写入name
还可以通过uname获得更多详细信息
int uname(struct utsname *name);
struct utsname的成员如下
下面是书上给出的一个示例程序和运行结果
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <pwd.h>
int main()
{
char computer[256];
struct utsname uts;
if(gethostname(computer,255)!=0||uname(&uts)<0)
{
fprintf(stderr,"error\n");
exit(1);
}
printf("Computer host name is %s\n",computer);
printf("System is %s on %s hardware\n",uts.sysname,uts.machine);
printf("Nodename is %s\n",uts.nodename);
printf("Version is %s, %s\n",uts.release,uts.version);
return 0;
}
可以看到主机的网络名、硬件名和系统信息都输出了,除此之外,还可以通过gethostid获得主机的唯一标识符
日志
日志通常用来记录各种应用程序的活动记录,包括一系列的错误和警告信息,一般来说,日志文件都存储在/usr/adm或/var/log中,系统为产生日志信息提供了一个接口
void syslog(int priority, const char *message, arguments...);
//向系统日志发送一条消息,每条消息有一个priority参数,该参数是一个严重级别与一个设施值的按位或,
//严重级别不同,系统对信息采取的措施就不痛
还有一些其他的能够改变日志记录的函数,如closelog、openlog、selogmask等
资源和限制
Linux的程序在运行的时候需要各种各样的资源,包括硬件资源,使用时间限制等,OS方面的显示常量在limits.h当中,除此之外,还有对资源操作的函数,如程序长度、执行优先级等,具体的函数原型如下
int getpriority(int which, id_t who);
//which是要查询的优先级类型如进程或进程组,who是要查询的ID,id_t是整数类型,用于标识用户和组标识符
int setpriority(int which, id_t who, int priority);
int getrlimit(int resource, struct rlimit *r_limit);
//resource:指定要查询的资源类型,如RLIMIT_CPU(CPU时间限制)、RLIMIT_FSIZE(文件大小限制)等。
//r_limit:指向rlimit结构的指针,用于存储查询到的资源限制信息
int setrlimit(int resource, const struct rlimit *r_limit);
int getrusage(int who, struct rusage *r_usage);
//who:指定要查询的对象,常见值有RUSAGE_SELF(查询当前进程)、RUSAGE_CHILDREN(查询当前进程的子进程)等。
//r_usage:指向rusage结构的指针,用于存储查询到的资源使用情况
下面是给出的一个例子和运行的结果
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <math.h>
void work()
{
FILE *f;
double x=4.5;
f=tmpfile();//创建临时文件
for(int i=0; i<10000; i++)
{
fprintf(f,"Output\n");//输出到临时文件
if(ferror(f))//如果超过长度
{
fprintf(stderr,"error memory");
exit(1);
}
}
for(int i=0; i<1000000; i++)
x++;
}
int main()
{
struct rusage r_usage;//确定程序耗费的时间,使用的用户时间和系统时间
struct rlimit r_limit;//设置程序运行的限制
int priority;
work();
getrusage(RUSAGE_SELF,&r_usage);//返回程序的使用信息
printf("CPU usage: User = %ld.%06ld, System = %ld.%06ld\n",r_usage.ru_utime.tv_sec, r_usage.ru_utime.tv_usec
,r_usage.ru_stime.tv_sec,r_usage.ru_stime.tv_usec);
priority=getpriority(PRIO_PROCESS,getpid());//获得优先级
printf("Current priority = %d\n",priority);
getrlimit(RLIMIT_FSIZE,&r_limit);//获得限制信息
printf("Current FSIZE limit: soft = %ld, hard = %ld\n",r_limit.rlim_cur,r_limit.rlim_max);
r_limit.rlim_cur=2048;//设置限制
r_limit.rlim_max=4096;
printf("Setting a 2K file size limit\n");
setrlimit(RLIMIT_FSIZE,&r_limit);
work();
return 0;
}
运行程序可以看到消耗的资源以及程序运行的优先级,当设置了文件大小限制的时候,程序就不能往临时文件写入多余2MB了,当使用nice启动程序来改变程序的优先级,程序的执行时间变长了,可以看到第一次work的时候由于没有设置文件大小,因此可以顺利的运行程序,但是当设置了文件大小之后,work便无法运行了
总结
本章介绍了Linux环境相关的内容,主要是一些系统设置好的函数和结构体以及它们的使用,例如时间、信息、系统运行等
参考文献
- 《Linux程序设计(第4版)》