开头先介绍man命令常用用法,以后会经常使用:
注:man手册分为多个section,每个section用一个字符表示。
这里的section可以理解为类别,例如:man 1 passwd 和 man 5 passwd是不一样的类别(详细见表一)。通常我们使用man passwd,这个时候man就按照预先设置的搜索路径和顺序去搜索passwd,当搜索到一个就停止继续搜索并将结果显示出来,如果我们指定了section,那么man只会在指定的section里去查找man帮助页。
1.who命令能做什么
如果想知道有谁在使用linux系统,输入who命令
从左到右分别为:用户名、主机名、登录时间、登录的域名host
2.分析who命令的工作过程
- 输入
man who
看到描述中有一段 - 猜想
/var/run/utmp
是保存相关数据的,然后用man -k utmp
- 看到描述中有
login records
,再输入man 5 utmp
,当中提到utmp.h
,打开vim /usr/include/utmp.h
- 关键就是上面那个头文件(存放了数据结构体),以及四个等价的
UTMP_FILE
宏定义(代表/var/run/utmp
这个路径,在path.h
中有提到) - 再进入
vim /usr/include/bits/utmp.h
中查看,这一段就是我们要的
ut_type:登录类型
ut_line:主机名
ut_user : 用户名(也叫#define ut_name ut_user)
ut_host:登录域名
ut_time : 登录时间 //是 ut_tv.tv_sec 定义在/usr/include/bits/time.h 即ut_time是int_32类型
注:
linux下存储时间常见的有两种存储方式,一个是从1970年到现在经过了多少秒(timeval),一个是用一个结构来分别存储年月日时分秒的(tm)。
常用时间转换函数:char *ctime(const time_t *timep);
与stuct tm* localtime(const time_t *timep);
具体参见时间类型详解 time_t
到现在,已经大概知道who命令是怎么执行的了:就是读取文件 “/var/rum/utmp” 然后显示出来,数据通过保存结构体 utmp 来保存。现在存放登录数据的文件路径有了,对应数据结构也有了,接下来就是实践。
3.如何编写程序
思考两个问题:如何从读取文件?如何将结构体中的信息按照一定格式输出?
答:使用三个系统调用:
①打开一个文件: open
这个系统调用在进程和文件之间建立一条连接,这个连接被称为文件描述符,它就像一条由进程通向内核的管道
②从文件读取数据: read
③关闭文件: close
4.开始编写程序
#include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<time.h>
#include<stdlib.h>
void show_info(utmp* Putmp)
{
if(Putmp->ut_type!=USER_PROCESS)//去除空白登录
return;
printf("%-8s",Putmp->ut_name);//左对齐,若字符串长度小于8,则右补空格,大于8则完整输出
printf(" ");
printf("%-8s",Putmp->ut_line);//主机名
printf(" ");
long my_time=Putmp->ut_time;//将ut_time转为long类型
tm* longin_time;//指向结构体tm的指针
login_time=localtime(&my_time);
printf("%d",login_time->tm_year+1900);//时间是从1900开始算,具体看文章开头的介绍
printf("-");
printf("%02d",login_time->tm_mon+1);//月份是0-11,所以要+1
printf("-");
printf("%02d", login_time->tm_mday);//如果不足2位,则左边补0
printf(" ");
printf("%02d", login_time->tm_hour);
printf(":");
printf("%02d", login_time->tm_min);
printf(" ");
printf("(%s)", Putmp->ut_host);
printf("\n");
}
int main()
{
utmp current_record;//保存数据的结构体
int fd;//文件描述符
int size=sizeof(current_record);//要读取的字节大小
if((fd=open(UTMP_FILE,O_RDONLY))==-1)//如果打开出错
{
perror(UTMP_FILE);
exit(1);
}
while(read(fd,¤t_record,size)==size)//读取文件
show_info(¤t_record);//按照格式输出
close(fd);//关闭文件
return 0;
}
运行结果 和系统的一模一样
5.总结
要善用man手册,以及结合管道符和grep指令的筛选,并介绍了三个基本的系统调用:open、read、close