利用stat函数实现ls- l filename功能

功能&函数介绍

在我们使用 ls -l 命令的时候,会输出下面的结果:

在这里,我使用stat函数来实现这一功能。我们知道,stat函数提供了一个结构体,其内容为:

struct stat {    dev_t         st_dev;       //文件的设备编号    ino_t         st_ino;       //节点    mode_t        st_mode;      //文件的类型和存取的权限    nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1    uid_t         st_uid;       //用户ID    gid_t         st_gid;       //组ID    dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号    off_t         st_size;      //文件字节数(文件大小)    blksize_t     st_blksize;   //块大小(文件系统的I/O 缓冲区大小)    blkcnt_t      st_blocks;    //块数    time_t        st_atime;     //最后一次访问时间    time_t        st_mtime;     //最后一次修改时间    time_t        st_ctime;     //最后一次改变时间(指属性)};

基于此,我们就可以获取到我们想要的信息。
根据图片中使用ls -l获得到的信息,我们知道我们需要获得的是块数、文件的类型和存取的权限、硬连接数目、用户ID、组ID、最后一次修改时间、文件名等信息。接下来我们可以分别来进行写。

下面开始讲解这个程序。

头文件

首先是所用到的头文件

#include <stdio.h> //perror和printf等使用#include <unistd.h> //stat使用#include <sys/stat.h> //stat使用#include <sys/types.h> //stat、getpwuid、getgrgid使用#include <pwd.h>//getpwuid使用#include <grp.h>//getgrgid使用#include <time.h> //输出时间ctime使用#include<stdlib.h> //exit使用

文件类型

我们知道,首先要显示的是文件类型,而文件类型则可使用stat结构体中的st_mode与上S_IFMT即可得到相应的文件类型,然后我们可以使用一个switch判断即可。相应的文件类型分别是:

字符表示八进制表示文件类型名称
S_IFSOCK0140000套接字
S_IFLINK0120000符号链接(软连接)
S_IFREG0100000普通文件
S_IFBLK0060000块设备
S_IFDIR0040000目录
S_IFCHR0020000字符设备
S_IFIFO0010000管道

代码实现可以写为(因为是示例,所以就用两种常用的文件格式来演示):

switch(st.st_mode & S_IFMT) { case S_IFREG: printf("-"); break; case S_IFDIR: printf("d"); break; }

权限输出(以及输出.)

我们知道用户、用户组、其它的权限信息仍然是保存在st._st_mode中,且其分布分别是:

  • 所有者权限(6~8bit)
  • 所属组权限(3~5bit)
  • 其它人权限(0~2bit)

所以我们可以通过取某一位,然后判断其除以3的余数情况来判断其是否可以读写执行。由于我们输出顺序为所有者、所有者的用户组、其他人,所以我们要先从高位进行取,取的方式就是将st_mode与某一位为1其它位为0的数字相与(为1的位置就是我们想取的那个位置),然后进行判断。若该部分余0,则说明没有该权限,所以为“-”代码如下:

int i = 0; for(i = 8;i >= 0;i--) { if(st.st_mode & (1 << i)) { switch(i%3) { case 2: printf("r"); break; case 1: printf("w"); break; case 0: printf("x"); break; } } else printf("-"); }

用于在权限后面有一个“.”,所以我们直接输出它:

printf(".");

硬连接数目

接下来就是要输出硬链接的数目,直接读取结构体中的st_nlink即可。所以实现代码如下(记得前面有一个空格):

int hardlin = (int)st.st_nlink; printf(" %d",hardlin);

输出所有者名

由于结构体中存储的是用户的uid,所以要想输出所有者名,我们就需要根据uid来获取用户的用户名。我们可以利用读取/etc/passwd进行查找,也可以采用下面的方法,即使用getpwuid()这个函数。

基础知识
函数原型:

struct passwd *getwnam(const char *name);struct passwd *getwuid(uid_t uid);
  • 关于getwnam
项目内容
功能用来逐一搜索参数name 指定的账号名称, 找到时便将该用户的数据以passwd 结构返回。
返回值返回 passwd 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生。
  • 关于getpwuid
项目内容
功能用来逐一搜索参数uid指定的用户识别码, 找到时便将该用户的数据以passwd结构体返回。
返回值返回 passwd 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生。
  • 关于passwd结构体
struct passwd{ char * pw_name; //用户账号 char * pw_passwd; //用户密码 uid_t pw_uid; //用户识别码 gid_t pw_gid; //组识别码 char * pw_gecos; //用户全名 char * pw_dir; //家目录 char * pw_shell; //所使用的shell 路径};

根据以上,我们可以直接写出以下代码:

struct passwd *pw = getpwuid(st.st_uid); printf(" %s",pw->pw_name);

输出所属组名

同上面一样,结构体里面只提供了所属组的GID,所以我们需要根据GID去查找所属组名。方法依然有两种,一为读取/etc/passwd进行查找;二为使用getgrgid()函数进行返回。

基础知识
函数原型:

struct group *getgrnam(const char *name);struct group *getgrgid(gid_t gid);
  • 关于getgrnam
项目内容
功能用来以指定的用户组名来搜索组文件, 找到时便将该组的数据以group 结构返回。
返回值返回 group 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生。
  • 关于getgrgid
项目内容
功能用来以指定的gid参数来逐一搜索组文件, 找到时便将该组的数据以group 结构返回。
返回值返回 group 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生。
  • 关于group结构体
struct group{ char *gr_name; //组名称 char *gr_passwd; //组密码 gid_t gr_gid; //组识别码 char **gr_mem; //组成员账号}

所以实现代码如下:

struct group *gp=getgrgid(st.st_gid); printf(" %s",gp->gr_name);

输出文件大小、最后修改时间、文件名

文件大小在stat的结构体里面为st.st_size

输出最后修改时间我们可以直接使用结构体里面的st.st_mtime即可,但是要转换成标准的日期输出方式,我们就需要用到ctime()函数。

其原型如下:

char *ctime(const time_t *timep);

其功能为:ctime()将参数timep所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果以字符串形态返回。此函数已经由时区转换成当地时间,字符串格式为”Wed Jun 30 21 :49 :08 1993\n”。

所以可以直接写出如下代码:

printf(" %s %s\n",ctime(&(st.st_mtime)),argv[1]);

至此,我们使用stat实现ls-l filename功能的程序便写完了。
我们的程序的运行结果如下:

代码参见这里

若显示存在问题,请参见《利用stat函数实现 ls -l filename功能》