全部博文(2759)
分类: LINUX
2015-03-21 09:19:22
原文地址:文件访问stat、fstat和lstat函数比较 作者:雪山飞狐_87
这三个函数的功能是一致的,都用于获取文件相关信息,但应用于不同的文件对象。对于函数中给出pathname参数,
函数返回与此命名文件有关的信息结构,函数获取已在描述符fields上打开文件的有关信息,函数类似于但
是当命名的文件是一个符号链接时,返回该符号链接的有关信息,而不是由该符号链接引用文件的信息。第二个参数
buf是指针,它指向一个用于保存文件描述信息的结构,由函数填写结构内容。该结构的实际定义可能随实现有所不同。
定义:
#include
int (const char *path, struct *buf);
int (int filedes, struct *buf);
int (const char *path, struct *buf);
参数:
path:文件路径名。
filedes:文件描述词。
buf:是以下结构体的指针
struct {
mode_t st_mode; //(文件保护模式)文件类型和权限信息
ino_t st_ino; //文件结点号
dev_t st_dev; //文件所在设备的文件系统标识号 device number (file system)
dev_t st_rdev; //文件所表示的特殊设备文件的设备标识 device number for special files
nlink_t st_nlink; //符号链接数
uid_t st_uid; //文件用户标识 用户ID
gid_t st_gid; //文件用户组标识 组ID
off_t st_size; // 总大小,字节为单位 size in bytes,for regular files
time_t st_st_atime; //文件内容最后访问的时间
time_t st_mtime; //文件内容最后修改时间
time_t st_ctime; //文件结构最后状态改变时间
blksize_t st_blksize; // 文件系统的最优I/O块大小 best I/O block size
blkcnt_t st_blocks; //分配给文件的块的数量,512字节为1单元 number of disk blocks allocated
};
文件类型:
普通文件,目录文件,块设备文件,字符设备文件,套接字,FIFO,符号链接.
文件类型信息包含在结构的st_mode成员中,可以用如下的宏确定文件类型,这些宏是结构中st_mode的成员.
S_ISREG();
S_ISDIR();
S_ISBLK();
S_ISCHR();
S_ISSOCK();
S_ISFIFO();
S_ISLNK();
应用一:判断文件(文件夹)是否可读、可写、可执行:
判断文件(文件夹)是否可读的函数:
#include
/** /brief 判断文件(文件夹)在当前上下文环境下是否可读* *
{ struct stat buff;
{ /**当前用户为root,当然拥有读的权限*/ if(0 == geteuid())
{ return 1;} /**当前用户为该文件(文件夹)的所有者,判断是否有所有者可读权限*/ else if(buff.st_uid == geteuid())
{ return ((buff.st_mode & S_IRUSR != 0)?1 : 0);} /**当前用户组为该文件(文件夹)的用户组,判断是否有用户组可读权限*/
{ return ((buff.st_mode & S_IRGRP != 0)?1 : 0);} /**判断其他人是否有可读权限*/
函数的过程很简单,判断逻辑在注释中也写的很清楚了,需要包含的头文件:
#include
利用这个思路,判断可写,判断可运行的函数就很容易写出了。
下面是判断文件(文件夹)是否可写的函数:
view plaincopy to clipboardprint?
#include
/** /brief 判断文件(文件夹)在当前上下文环境下是否可写* *
{ struct stat buff;
{ /**当前用户为root,当然拥有写的权限*/ if(0 == geteuid())
{ return 1;} /**当前用户为该文件(文件夹)的所有者,判断是否有所有者可写权限*/ else if(buff.st_uid == geteuid())
{ return ((buff.st_mode & S_IWUSR != 0)?1 : 0);} /**当前用户组为该文件(文件夹)的用户组,判断是否有用户组可写权限*
{ return ((buff.st_mode & S_IWGRP != 0)?1 : 0);} /**判断其他人是否有可读权限*/
下面是判断文件(文件夹)是否可运行的函数:
view plaincopy to clipboardprint?
#include
/** /brief 判断文件(文件夹)在当前上下文环境下是否可执行* *
{ struct stat buff;if(stat(_path,&buff) == 0)
{ /**当前用户为root,当然拥有读的权限*/ if(0 == geteuid())
{ return 1;} /**当前用户为该文件(文件夹)的所有者,判断是否有所有者可执行权限*/
{ return ((buff.st_mode & S_IXUSR != 0)?1 : 0);} /**当前用户组为该文件(文件夹)的用户组,判断是否有用户组可执行权限*
{ return ((buff.st_mode & S_IXGRP != 0)?1 : 0);} /**判断其他人是否有可执行权限*/
应用二:获得文件(文件夹)的大小
#include
/** /brief 获得文件夹的总大小* *
{ struct dirent* ent(0);DIR* pDir(opendir(_path));
{ /**在Linux文件系统中 .和..也是特殊的子目录,明显这里不应该计算*/
{ continue;}
{ result += getDirTotalSize(buff);}
/** /brief 获得文件的大小* *
{ struct stat buff;if (stat(_path, &buff) == 0)
{ return buff.st_size;} else { return -1;}
其实更加通用的遍历目录函数可以这样设计:用注册回调函数的方法来实现,这个回调函数的参数就是每个遍历项的路径(最好是绝对路径),
#include
off_t getFileSize(const char* _path);void traverseDir(constchar* _path,off_t(*_callPtr)(constchar*),void(*_callbackResPtr)(off_t) = 0);void sumSize(off_t _size);
/**< 计算的文件夹大小结果 */ off_t result(0);
{ traverseDir(*(++argv),getFileSize,sumSize);
/** /brief 递归遍历目录,并在遇到非文件夹时* 调用回调函数off_t(*_callPtr)(const char*)
{ struct dirent* ent(0);DIR* pDir(opendir(_path));char buff[512] = {0};while ((ent = readdir(pDir)) != 0)
{ /**在Linux文件系统中 .和……也是特殊的子目录,明显这里不应该递归*/
{ continue;} sprintf(buff, "%s/%s", _path, ent->d_name);
{ traverseDir(buff,_callPtr,_callbackResPtr);} else { if(_callbackResPtr)
{(*_callbackResPtr)( (*_callPtr)(buff) );} else {(*_callPtr)(buff);} return;}
/** /brief 获得文件的大小* * /param const char* _path: 文件的路径,可以为绝对路径或相对路径*
{ struct stat buff;if (stat(_path, &buff) == 0)
{ return buff.st_size;} else { return -1;}
/** /brief 一个简单的统计,把每次传入的数值累加起来 赋值到result上* * /param off_t _size: 文件的大小*
{ result += _size;return;}
应用三:获得文件(文件夹)的三个时间:
最后访问(读)时间:文件(文件夹)最后一次被存取或执行的时间;
最后修改(写)时间:文件(文件夹)最后一次被修改的时间,这里指的修改是内容上的;
创建时间或最后更改(属性更改)时间:文件(文件夹)最后一次被更改的时间,这里指的修改是属性上的,如所有者、权限等;
对应到结构体stat上就是:
time_t st_atime; /* time of last access */
值得一提的是,以上三种时间在Linux中是用UTC表示的,单位是秒,
这里仅用最后访问(读)时间为例:
view plaincopy to clipboardprint?
#include
/** /brief 判断文件(文件夹)的最后访问时间* * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径*
{ struct stat buff;if(stat(_path,&buff) == 0)
{ return buff.st_atime;} return 0;}另外两种时间的获取方式,就当作小练习吧。
应用四:获得文件类型
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) /**文件夹的判断*/
实例如下:
view plaincopy to clipboardprint?
#include
/** /brief 判断文件(文件夹)的类型* * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径*
{ struct stat buff;if(stat(_path,&buff) == 0)
{ if(S_ISREG(buff.st_mode))
{ return 0;} else if(S_ISDIR(buff.st_mode))
{ return 1;} else if(S_ISFIFO(buff.st_mode))
{ return 2;} else if(S_ISCHR(buff.st_mode))
{ return 3;} else if(S_ISBLK(buff.st_mode))
{ return 4;} else { return -1;} else { return -1;}
返回说明:
成功执行时,返回0;失败返回-1,errno被设为以下的某个值
EBADF: 文件描述词无效
EFAULT: 地址空间不可访问
ELOOP: 遍历路径时遇到太多的符号连接
ENAMETOOLONG:文件路径名太长
ENOENT:路径名的部分组件不存在,或路径名是空字串
ENOMEM:内存不足
ENOTDIR:路径名的部分组件不是目录
#include
#include
/param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径*
/return signed char * 1:可读;* 0:不可读;* -1:错误,错误号可以从全局的errno获取;
*/ signed char canRead(constchar* _path)
if(stat(_path,&buff) == 0)
else if(buff.st_gid == getegid())
else { return ((buff.st_mode & S_IROTH != 0)?1 : 0);}
else { return -1;}
/param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径*
/return signed char * 1:可读;* 0:不可读;* -1:错误,错误号可以从全局的errno获取;*
/ signed char canWrite(constchar* _path)
if(stat(_path,&buff) == 0)
/ else if(buff.st_gid == getegid())
else { return ((buff.st_mode & S_IWOTH != 0)?1 : 0);} else { return -1;}
/param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径*
/return signed char * 1:可读;* 0:不可读;* -1:错误,错误号可以从全局的errno获取;*
/ signed char canExecute(constchar* _path)
else if(buff.st_uid == geteuid())
/ else if(buff.st_gid == getegid())
else { return ((buff.st_mode & S_IXOTH != 0)?1 : 0);} else { return -1;}
对于普通文件来说,获取文件占用的大小很简单,只需要返回结构体stat的st_sizee即可。
但是对于文件夹来说,结构体stat的st_size表明的是文件夹本身占用的空间大小(在Linux文件体系中,对于文件夹来说是需要空间来自身
文件夹下的文件或文件夹的inode号的),与我们普遍意义上理解的文件夹应该返回的是其包含文件或文件夹的总容量不同,因此需要设计一个函
数来获得文件夹下所有文件(文件夹)的总容量:
/param const char* _path: 文件夹的路径,可以为绝对路径或相对路径*
/return off_t * 返回路径指向文件夹的总容量;*
/ off_t getDirTotalSize(constchar* _path)
off_t result(0);char buff[512] = {0};
while ((ent = readdir(pDir)) != 0)
if(strcmp(ent->d_name,".") == 0 || strcmp(ent->d_name,"..") == 0)
sprintf(buff, "%s/%s", _path, ent->d_name);
/**如果当前是目录 则递归计算子目录的大小*/ if (ent->d_type == DT_DIR)
else { result += getFileSize(buff);}
return result;}
/param const char* _path: 文件的路径,可以为绝对路径或相对路径*
/return off_t * 成功则返回路径指向文件的大小;* -1:错误,错误号可以从全局的errno获取;*
/ off_t getFileSize(const char* _path)
那么以后遍历目录就不需要改变了 只需要在应用中注册不同的回调函数就可以了。实现如下:
int main(int argc,char** argv)
printf("%ld", result);return 0;}
参数为当前的绝对路径* * /param const char* _path:
需要遍历的文件夹的路径,可以为绝对路径或相对路径* /param off_t(*_callPtr)(const char*):*
需要遍历的文件夹的路径,可以为绝对路径或相对路径* /param void(*_callbackResPtr)(off_t):*
以每次调用完_callPtr后的返回值为参数的回调函数,默认值为0,* 表示不对每次调用_callPtr的结果感兴趣*
/return void */ void traverseDir(constchar* _path,off_t(*_callPtr)(constchar*),void(*_callbackResPtr)(off_t))
if(strcmp(ent->d_name,".") == 0 || strcmp(ent->d_name,"……") == 0)
/**如果当前是目录 则递归子目录*/ if (ent->d_type == DT_DIR)
/return off_t * 成功则返回路径指向文件的大小;* -1:错误,错误号可以从全局的errno获取;*/ off_t getFileSize(const char* _path)
/return void */ void sumSize(off_t _size)
这种实现方式的优势是利用回调函数,遍历文件夹的操作可以复用,缺点是如果需要统计每次回调函数的结果就需要额外的一个全局参数
(当然可以用命名空间的方式局部化……)。利用这种方式,还能方便的实现出统计文件夹下各种文件类型的数量,属于某个用户ID文件的数量等等
(改改两个回调函数就行了)。
最后访问(读)时间、最后修改(写)时间、创建时间或最后更改(属性更改)时间.
在项目中,我们经常会需要获得文件(文件夹)的最后访问(读)时间、最后修改(写)时间、创建时间或最后更改(属性更改)时间这三种时间,
在Linux中,触发这三种时间改变的条件分别是:
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
举个例子:1285328411表示的是从1970年1月1日开始所经过的秒数,值得注意的是这里的时间是UTC时间。
/return time_t * >0:成功;* 0:错误;*/
time_t getReadTime(constchar* _path)
最后来谈谈如何根据st_mode来判断文件(文件夹)的类型,这里可以利用库本身就定义好的一些宏:
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /**管道文件的判断*/
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /**字符设备的判断*/
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /**块设备的判断*/
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) /**普通文件的判断*/
/return signed char * 0:普通文件* 1:文件夹* 2:管道文件* 3:字符设备文件* 4:块设备文件* -1:错误,
错误号可以从全局的errno获取;*/ signed char getFileType(constchar* _path)
当然在项目中一般是不用硬编码的,可以定义相关的enum.