Implement Unix / Linux programming practice reading tutorial notes -who instruction (achieved under Mac) - Notes from the second chapter of P25-P44

Before implementing command who must first understand its function:

  who command to view information about the user currently logged on, including user name, terminal name and login time, try it first on their computers:

  

  After a review of the online help documentation on the book clearly one thing: who presented information from the / var / adm / utmp file, on further review by the book that, utmp This file is stored in a structure array , this structure is /usr/include/utmp.h defined in this header file, the following is utmp.h on my computer:

Among them, the user name is stored ut_name, ut_line preservation is terminal name, ut_time save login time, ut_host save the name of the remote computer used to log in.

 

 

So how do we read this document?

  Before the first chapter we used fgets, it can read a specified number of characters, but it does not apply to the current situation, the specific Why not book says. I checked a lot of information, ambiguous feelings are put under I said I understand it, because the current is to be read a data structure, and fgets are ways to handle text to read the content, it may put when text data processing, which is not so sure, can only say that the general fgets to read the text of it. Another point is that when fgets to read, if the file contains the number of characters is not enough, the system will automatically fill in the back '\ 0', and perhaps this is not conducive to processing of the data.

  Read function is selected read function, the contents of the buffer when it is in the form of binary data to processing for reading a data block, such as an array or structure. Function prototype is as follows:

size_t numread = read(int fd,void *buf,size_t qty)

  This function is located in the header file <unistd.h>, the system call, you can check usage yourself, the point to note is the number of bytes actually read read returned if the file contains the number of characters is not enough, the system will not automatically back up '\ 0'.

  I think another very important point is to read the complete kernel-based buffer, which is different with fgetc, which is a standard function, based on user-space buffer is implemented. Specifically refer to the article: https: //traxexer.iteye.com/blog/1725145

  Put a few in reference links when I looked up and read fgets difference:

   https://blog.csdn.net/yanglianzhuang/article/details/83546696

  https://zhidao.baidu.com/question/141607019.html

  https://bbs.csdn.net/topics/90053721

  http://blog.chinaunix.net/uid-21377953-id-443333.html

  To be honest, I still do not understand why not use fgets and read, read data processing fgets do with text will result in garbled? The first matter, I would like to remember to use it when read by fgets when reading a bunch of text, read data block.

  

  Well, using the Read to read the data, but to use the file descriptor read function, we open the file corresponding to the function selected on the Open, which is included in the header <fcntl.h>, in particular profiles in the first Chapter notes recorded over, do not write. Note that even open for the same file multiple times, corresponding to the file descriptor is different .

  Next is very simple, we briefly describe the process:

  1. Open the first utmp file, remember to turn on when taking into account the open failed.

  2. The loop reads the file, the number of bytes is noted that the read exactly equal to the size of the structure . Each read a structure, it will be parsed and information output on the screen. When will it end? Because read returns the number of bytes actually read, so when a certain number of bytes read is not specified it can be pulled out.

  3. Do not forget to close the file and exit.

 

Now show the code book (I did some small changes):

#include <stdio.h>
#include <fcntl.h>
#include <utmp.h>
#include <unistd.h>

#define SHOWHOST

void show_info(struct utmp *utbufp);

int main(int argc,char *argv[])
{
    int FD;
     // open utmp file, UTMP_FILE utmp.h defined, the file path indicates the 
    IF ((FD = Open (UTMP_FILE, of O_RDONLY)) == - . 1 )
    {
        exit(1);
    }
    struct utmp current_record;
     int reclen = the sizeof (current_record);
     // loop reads each file structure utmp array structure analysis processing and 
    the while (Read (FD, & current_record, reclen) == reclen)
    {
        show_info(&current_record);
    }
    close(fd);
    return 0;
}

// show to read the utmp structure 
void show_info ( struct utmp * utbufp)
{
    // username 
    printf ( " % -8.8s " , utbufp-> ut_name);
    the printf ( "  " );
     // terminal name 
    the printf ( " % -8.8s " , utbufp-> ut_line);
    printf ( "  " );
     // login time 
    printf ( " % 10LD " , utbufp-> ut_time);
    printf(" ");
#ifdef SHOWHOST
    // remote host name 
    printf ( " (% S) " , utbufp-> ut_host);
 #endif 
    printf ( " \ the n- " );
}

  Although the book it will do follow-up optimization, but this version at least should be able to run, but I practice Shique fell into the pit, as shown below:

  首先是UTMP_FILE这个宏定义不存在,看了一下utmp.h文件,发现确实没有这个定义,看样子基于Unix系统的Mac和Linux下的utmp文件配置还是有点不同,那我们看一下这个文件被放到哪去了: 

  

  这里需要注意一下这个grep指令,它可以在指定目录下查找包含匹配字符串的文件内容,-n选项代表显示的内容中包含行号,比如上图,可以看到上面显示的每一个条中间都有个数字,33,18,20,12这些都是在此文件中的行号,-R代表递归地对目录下的所有文件(包括子目录)进行 grep。

  为什么要在usr/include文件夹下查找呢?因为库函数一般存这个文件夹下,而且utmp.h在这个文件夹下,想来与它相关的宏定义也会在这里吧。

  上图中可以看到UTMP_FILE被定义在netbsd.h和freebsd.h中,它与PATH_UTMP这个宏定义被关联起来了,而_PATH_UTMP在utmp.h中被定义为:#define _PATH_UTMP "/var/run/utmp",因此很简单,我们直接把UTMP_FILE改为PATH_UTMP就行。

  问题解决了吗?

  并没有,“Definition of 'utmp' must be imported from module 'Darwin.C.util' before it is required”这个错误仍然在那里,我看了许多网上的帖子也没有找到靠谱的办法,后来偶尔发现直接运行居然就不报错了。。

  然而仍有问题,我的程序什么都没有输出。。。

  又是一番调试,我发现读取到的结构体根本就是空的,于是我去了/var/run/utmp目录下找utmp这个文件,发现没有!只有个叫utmpx的。

  咋办呢?我又去看了下utmp.h,发现上面有这么一段话:

  看样子在Mac下utmp.h已经被弃用了,现在用的是utmpx,我们去/usr/include下找到utmpx.h,打开看下: 

  这个文件内容比较多,我们就挑有用的看就行,现在问题是_PATH_UTMP对应路径下没有utmp这个文件,显然我们要把路径改为指向utmpx这个文件,刚才我们已经看到它在/var/run下,找一下utmpx.h中有没有对应路径的宏定义,发现有个_PATH_UTMPX,显然需要在代码中把路径改为这个。

  我们把包含的头文件utmp.h改为utmpx.h,仔细看发现utmpx.h中结构体定义名为utmpx,且对应的结构体子项和utmp.h中定义的的名字不大相同,因此还要修改结构体的定义,好在看它们的名字,还是很容易联系上的。有一点要注意,登录时间这一项,原先是个long,而现在是个结构体struct timeval ut_tv。

怎么从这个结构体中提取出对应的long呢?我们在utmpx.h中没发现struct timeval的定义,在xcode中跳转一下,我们找到如下内容:

看起来tv_sec的可能性比较大,再跳转一下:

很好,看样子这个tv_sec就对应原先的ut_time了。

 

因此修改后的代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <utmpx.h>
#include <unistd.h>

#define SHOWHOST

void show_info(struct utmpx *utbufp);

int main(int argc,char *argv[])
{
    int fd;
    //打开utmpx文件,UTMP_FILEX定义在utmpx.h中,指示了文件路径
    if((fd = open(_PATH_UTMPX,O_RDONLY)) == -1)
    {
        exit(1);
    }
    struct utmpx current_record;
    int reclen = sizeof(current_record);
    //循环读取utmpx文件中结构体数组中的每一个结构体并解析处理
    while (read(fd,&current_record,reclen) == reclen)
    {
        show_info(&current_record);
    }
    close(fd);
    return 0;
}

//展示读取到的utmpx结构体
void show_info(struct utmpx *utbufp)
{
    //用户名
    printf("% -8.8s",utbufp->ut_user);
    printf(" ");
    //终端名
    printf("% -8.8s",utbufp->ut_line);
    printf(" ");
    //登录时间
    printf("% 10ld",utbufp->ut_tv.tv_sec);
    printf(" ");
#ifdef SHOWHOST
    //远程主机名
    printf("( %s)",utbufp->ut_host);
#endif
    printf("\n");
}

这次代码成功运行了,但结果。。。看下图吧:

这结果莫名其妙,为了解决这个问题,我们去看一下utmpx这个文件的内容,结果发现编辑器打不开,这似乎不是UTF-8编码的,我们把后缀改为.txt再看一下:

看样子我们打印出的就是这个文件的第一行了,后面也能看到对应的登录名czw52460183和终端名ttys,但这个文件显然编码方式不对,出现了乱码,这什么情况?

网上也没有解释,找了半天打算放弃了,结果偶然在utmpx.h中发现这段话:

意思是我们不能直接去读取utmpx这个文件了,底层统一封装了获取utmpx结构体的方法,名为getutxent,我们看一下它的联机帮助文档:

这方法厉害了,按描述看,它就和读取文本一样,读完后应该有个类似记录当前位置的机制,再次调用时会读取数组中下一个utmpx结构体,而且它不需要文件事先被打开,如果没打开,它会自动打开文件。

真是日了狗了,那之前搞那么多打开文件和读取文件的步骤就都不需要了,其实实现这个who指令目的就是要练习文件的打开啊,现在你强行给我做了个封装,我尝试去跳转找getutxent的源码,然而并没找到。。。算了,那就直接用这个吧,修改后的代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <utmpx.h>
#include <unistd.h>
#include <time.h>

//#define SHOWHOST

void show_info(struct utmpx *utbufp);
void showtime( long timeval);

int main(int argc,char *argv[])
{
    //int fd;
    //打开utmpx文件,UTMP_FILEX定义在utmpx.h中,指示了文件路径
//    if((fd = open(_PATH_UTMPX,O_RDONLY)) == -1)
//    {
//        exit(1);
//    }
//    struct utmpx current_record;
//    int reclen = sizeof(current_record);
    //循环读取utmpx文件中结构体数组中的每一个结构体并解析处理
    //while (read(fd,&current_record,reclen) == reclen)
    while (getutxent() != NULL)
    {
        //show_info(&current_record);
        show_info(getutxent());
    }
    //close(fd);
    return 0;
}

//展示读取到的utmpx结构体
void show_info(struct utmpx *utbufp)
{
    //只显示已登录用户
    if(utbufp->ut_type != USER_PROCESS)
    {
        return;
    }
    //用户名
    printf("% -12.12s",utbufp->ut_user);
    printf(" ");
    //终端名
    printf("% -8.8s",utbufp->ut_line);
    printf(" ");
    //登录时间
    //printf("% 10ld",utbufp->ut_tv.tv_sec);
    showtime(utbufp->ut_tv.tv_sec);
    printf(" ");
#ifdef SHOWHOST
    //远程主机名
    printf("( %s)",utbufp->ut_host);
#endif
    printf("\n");
}

//将时间戳转换为可读形式并输出
void showtime(long timeval)
{
    char *p;
    p = ctime(&timeval);
    //从第4个字符开始输出,屏蔽星期几的信息
    printf("%12.12s", p+4);
}

结果如下:

已经很接近自带的who输出结果了,现在还有四个问题:

  一是和书里不一样,我们自带的who没有输出远程主机名,对应的是图中的(),所以要注释掉#define SHOWHOST。

  二是我的用户名比书里的要长,因此输出的字符不能这样截断,要多留点空间,可以把用户名输出那一行改为:

     printf("% -12.12s",utbufp->ut_user);

补充一下, printf动态控制宽度的知识: printf("%-N.Ms",str);  它的作用是输出指定长度N的字符串,超长M时截断,不足时左对齐,右边补空格。

可以参考:https://blog.csdn.net/w332530494/article/details/8731921

  三是我们自带的who命令只输出了两项,而这里为什么输出了三项呢?

这是因为utmpx结构体数组包含了所有终端的信息,即使某个终端没有用到,也会存放进去,所以要过滤一下,只显示已登录的用户。

  utmpx结构体中有一个成员ut_type,当它的值为7(USER_PROCESS,定义在utmpx.h头文件中)时,表示这是一个已登录的用户,因此只要在show_info中判断出不是7就返回。

  四是我们时间显示目前仍是时间戳的形式,需要将它转换为可读的形式,可以使用ctime函数,函数原型如下:

  char *ctime(const time_t *timep);

  其中,time_t定义在time.h头文件中: typedef long int time_t;

  因此传入的是一个long *,返回的是该时间戳的可读形式,注意返回的可读形式会包含4个字符的星期几的信息,比如:Tue Jun  4 2,我们自带的who输出中没有星期几的信息,所以输出时要从第四个字符开始输出。

  综合以上四点,改进代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <utmpx.h>
#include <unistd.h>
#include <time.h>

//#define SHOWHOST

void show_info(struct utmpx *utbufp);
void showtime( long timeval);

int main(int argc,char *argv[])
{
    //int fd;
    //打开utmpx文件,UTMP_FILEX定义在utmpx.h中,指示了文件路径
//    if((fd = open(_PATH_UTMPX,O_RDONLY)) == -1)
//    {
//        exit(1);
//    }
//    struct utmpx current_record;
//    int reclen = sizeof(current_record);
    //循环读取utmpx文件中结构体数组中的每一个结构体并解析处理
    //while (read(fd,&current_record,reclen) == reclen)
    while (getutxent() != NULL)
    {
        //show_info(&current_record);
        show_info(getutxent());
    }
    //close(fd);
    return 0;
}

//展示读取到的utmpx结构体
void show_info(struct utmpx *utbufp)
{
    //只显示已登录用户
    if(utbufp->ut_type != USER_PROCESS)
    {
        return;
    }
    //用户名
    printf("% -12.12s",utbufp->ut_user);
    printf(" ");
    //终端名
    printf("% -8.8s",utbufp->ut_line);
    printf(" ");
    //登录时间
    //printf("% 10ld",utbufp->ut_tv.tv_sec);
    showtime(utbufp->ut_tv.tv_sec);
    printf(" ");
#ifdef SHOWHOST
    //远程主机名
    printf("( %s)",utbufp->ut_host);
#endif
    printf("\n");
}

//将时间戳转换为可读形式并输出
void showtime(long timeval)
{
    char *p;
    p = ctime(&timeval);
    //从第4个字符开始输出,屏蔽星期几的信息
    printf("%12.12s", p+4);
}

现在的输出结果如下:

 

现在来看,这个程序很简单,重要的是这次经历让我明白,一定要好好看头文件的说明!!!

很多改动,比如某个文件的弃用,已经明确说明在了头文件中,另外,联机帮助文档也很重要。

很多问题,网上是查不到解决方案的,这时就要静下心来,好好看头文件和联机帮助文档。

 

Guess you like

Origin www.cnblogs.com/czw52460183/p/10999434.html