初学Linux编程,相信很多朋友都和我一样,对于在Linux中如何获得文件相关属性,如文件大小、最后访问(读)时间、最后修改(写)时间、创建时间或最后更改(属性更改)时间等等存在着疑虑,花了点时间探究了下,发现其中的知识点挺多的,不是想象中的那么简单,于是写出来与大家分享一下。
Linux系统调用函数:stat、fstat、lstat
在Linux中,获得文件属性的系统调用函数有三个为stat、fstat、lstat,原型如下:
int stat(const char* file_name,struct stat* buf);
参数:
[in]file_name:文件(文件夹)的路径,相对路径和绝对路径都可以;
[out]buf:描述文件(文件夹)属性的结构体;
[return] 成功 0, 失败 -1。错误代码存在全局的errno中;
int fstat(int fd,struct stat* buf);
[in]fd:文件(文件夹)的标识符;
[out]buf:描述文件(文件夹)属性的结构体;
[return] 成功 0, 失败 -1。错误代码存在全局的errno中;
int lstat(const char* file_name,struct stat* buf);
[in]file_name:文件(文件夹)的路径,相对路径和绝对路径都可以;
[out]buf:描述文件(文件夹)属性的结构体;
[return] 成功 0, 失败 -1。错误代码存在全局的errno中;
lstat和stat的作用完全一样,区别在于如果传入lstat的路径为符号连接(“软连接”)时,获得的是符号连接本身的属性,而stat获得是符号连接目标文件(文件夹)的属性。
想要在程序中调用这三种方法,必须include的头文件有:
#include <sys/unistd.h>
#include <sys/stat.h>
结构体 stat
用man 2 stat,看下结构体stat的定义:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
官方的注释已经很清楚了。关于文件(文件夹)属性的信息都存在这里面的,下面就利用这个结构体,举例来说能做的一些应用。
判断文件(文件夹)是否可读的函数:
#include <sys/unistd.h> #include <sys/stat.h> #include <sys/types.h> /** /brief 判断文件(文件夹)在当前上下文环境下是否可读 * * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径 * /return signed char * 1:可读; * 0:不可读; * -1:错误,错误号可以从全局的errno获取; */ signed char canRead(const char* _path) { struct stat buff; if(stat(_path,&buff) == 0) { /**当前用户为root,当然拥有读的权限*/ if(0 == geteuid()) { return 1; } /**当前用户为该文件(文件夹)的所有者,判断是否有所有者可读权限*/ else if(buff.st_uid == geteuid()) { return ((buff.st_mode & S_IRUSR != 0)?1 : 0); } /**当前用户组为该文件(文件夹)的用户组,判断是否有用户组可读权限*/ else if(buff.st_gid == getegid()) { return ((buff.st_mode & S_IRGRP != 0)?1 : 0); } /**判断其他人是否有可读权限*/ else { return ((buff.st_mode & S_IROTH != 0)?1 : 0); } } else { return -1; } }
函数的过程很简单,判断逻辑在注释中也写的很清楚了,需要包含的头文件:
#include <sys/unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
利用这个思路,判断可写,判断可运行的函数就很容易写出了。
下面是判断文件(文件夹)是否可写的函数:
#include <sys/unistd.h> #include <sys/stat.h> #include <sys/types.h> /** /brief 判断文件(文件夹)在当前上下文环境下是否可写 * * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径 * /return signed char * 1:可读; * 0:不可读; * -1:错误,错误号可以从全局的errno获取; */ signed char canWrite(const char* _path) { struct stat buff; if(stat(_path,&buff) == 0) { /**当前用户为root,当然拥有写的权限*/ if(0 == geteuid()) { return 1; } /**当前用户为该文件(文件夹)的所有者,判断是否有所有者可写权限*/ else if(buff.st_uid == geteuid()) { return ((buff.st_mode & S_IWUSR != 0)?1 : 0); } /**当前用户组为该文件(文件夹)的用户组,判断是否有用户组可写权限*/ else if(buff.st_gid == getegid()) { return ((buff.st_mode & S_IWGRP != 0)?1 : 0); } /**判断其他人是否有可读权限*/ else { return ((buff.st_mode & S_IWOTH != 0)?1 : 0); } } else { return -1; } }
下面是判断文件(文件夹)是否可运行的函数:
#include <sys/unistd.h> #include <sys/stat.h> #include <sys/types.h> /** /brief 判断文件(文件夹)在当前上下文环境下是否可执行 * * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径 * /return signed char * 1:可读; * 0:不可读; * -1:错误,错误号可以从全局的errno获取; */ signed char canExecute(const char* _path) { struct stat buff; if(stat(_path,&buff) == 0) { /**当前用户为root,当然拥有读的权限*/ if(0 == geteuid()) { return 1; } /**当前用户为该文件(文件夹)的所有者,判断是否有所有者可执行权限*/ else if(buff.st_uid == geteuid()) { return ((buff.st_mode & S_IXUSR != 0)?1 : 0); } /**当前用户组为该文件(文件夹)的用户组,判断是否有用户组可执行权限*/ else if(buff.st_gid == getegid()) { return ((buff.st_mode & S_IXGRP != 0)?1 : 0); } /**判断其他人是否有可执行权限*/ else { return ((buff.st_mode & S_IXOTH != 0)?1 : 0); } } else { return -1; } }
对于普通文件来说,获取文件占用的大小很简单,只需要返回结构体stat的st_sizee即可。但是对于文件夹来说,结构体stat的st_size表明的是文件夹本身占用的空间大小(在Linux文件体系中,对于文件夹来说是需要空间来存储自身文件夹下的文件或文件夹的inode号的),与我们普遍意义上理解的文件夹应该返回的是其包含文件或文件夹的总容量不同,因此需要设计一个函数来获得文件夹下所有文件(文件夹)的总容量:
#include <sys/unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <dirent.h> #include <string> /** /brief 获得文件夹的总大小 * * /param const char* _path: 文件夹的路径,可以为绝对路径或相对路径 * /return off_t * 返回路径指向文件夹的总容量; */ off_t getDirTotalSize(const char* _path) { struct dirent* ent(0); DIR* pDir(opendir(_path)); off_t result(0); char buff[512] = {0}; while ((ent = readdir(pDir)) != 0) { /**在Linux文件系统中 .和..也是特殊的子目录,明显这里不应该计算*/ if(strcmp(ent->d_name,".") == 0 || strcmp(ent->d_name,"..") == 0) { continue; } sprintf(buff, "%s/%s", _path, ent->d_name); /**如果当前是目录 则递归计算子目录的大小*/ if (ent->d_type == DT_DIR) { result += getDirTotalSize(buff); } else { result += getFileSize(buff); } } return result; } /** /brief 获得文件的大小 * * /param const char* _path: 文件的路径,可以为绝对路径或相对路径 * /return off_t * 成功则返回路径指向文件的大小; * -1:错误,错误号可以从全局的errno获取; */ off_t getFileSize(const char* _path) { struct stat buff; if (stat(_path, &buff) == 0) { return buff.st_size; } else { return -1; } }
其实更加通用的遍历目录函数可以这样设计:用注册回调函数的方法来实现,这个回调函数的参数就是每个遍历项的路径(最好是绝对路径),那么以后遍历目录就不需要改变了 只需要在应用中注册不同的回调函数就可以了。实现如下:
#include <sys/unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <dirent.h> #include <string> #include <stdio.h> off_t getFileSize(const char* _path); void traverseDir(const char* _path,off_t(*_callPtr)(const char*),void(*_callbackResPtr)(off_t) = 0); void sumSize(off_t _size); /**< 计算的文件夹大小结果 */ off_t result(0); int main(int argc, char** argv) { traverseDir(*(++argv),getFileSize,sumSize); printf("%ld", result); return 0; } /** /brief 递归遍历目录,并在遇到非文件夹时 * 调用回调函数off_t(*_callPtr)(const char*) 参数为当前的绝对路径 * * /param const char* _path: 需要遍历的文件夹的路径,可以为绝对路径或相对路径 * /param off_t(*_callPtr)(const char*): * 需要遍历的文件夹的路径,可以为绝对路径或相对路径 * /param void(*_callbackResPtr)(off_t): * 以每次调用完_callPtr后的返回值为参数的回调函数,默认值为0, * 表示不对每次调用_callPtr的结果感兴趣 * /return void */ void traverseDir(const char* _path,off_t(*_callPtr)(const char*),void(*_callbackResPtr)(off_t)) { struct dirent* ent(0); DIR* pDir(opendir(_path)); char buff[512] = {0}; while ((ent = readdir(pDir)) != 0) { /**在Linux文件系统中 .和..也是特殊的子目录,明显这里不应该递归*/ if(strcmp(ent->d_name,".") == 0 || strcmp(ent->d_name,"..") == 0) { continue; } sprintf(buff, "%s/%s", _path, ent->d_name); /**如果当前是目录 则递归子目录*/ if (ent->d_type == DT_DIR) { traverseDir(buff,_callPtr,_callbackResPtr); } else { if(_callbackResPtr) { (*_callbackResPtr)( (*_callPtr)(buff) ); } else { (*_callPtr)(buff); } } } return; } /** /brief 获得文件的大小 * * /param const char* _path: 文件的路径,可以为绝对路径或相对路径 * /return off_t * 成功则返回路径指向文件的大小; * -1:错误,错误号可以从全局的errno获取; */ off_t getFileSize(const char* _path) { struct stat buff; if (stat(_path, &buff) == 0) { return buff.st_size; } else { return -1; } } /** /brief 一个简单的统计,把每次传入的数值累加起来 赋值到result上 * * /param off_t _size: 文件的大小 * /return void */ void sumSize(off_t _size) { result += _size; return; }
这种实现方式的优势是利用回调函数,遍历文件夹的操作可以复用,缺点是如果需要统计每次回调函数的结果就需要额外的一个全局参数(当然可以用命名空间的方式局部化。。。)。利用这种方式,还能方便的实现出统计文件夹下各种文件类型的数量,属于某个用户ID文件的数量等等(改改两个回调函数就行了)。
在项目中,我们经常会需要获得文件(文件夹)的最后访问(读)时间、最后修改(写)时间、创建时间或最后更改(属性更改)时间这三种时间,在Linux中,触发这三种时间改变的条件分别是:
最后访问(读)时间:文件(文件夹)最后一次被存取或执行的时间;
最后修改(写)时间:文件(文件夹)最后一次被修改的时间,这里指的修改是内容上的;
创建时间或最后更改(属性更改)时间:文件(文件夹)最后一次被更改的时间,这里指的修改是属性上的,如所有者、权限等;
对应到结构体stat上就是:
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
值得一提的是,以上三种时间在Linux中是用UTC表示的,单位是秒,举个例子:1285328411表示的是从1970年1月1日开始所经过的秒数,值得注意的是这里的时间是UTC时间。
这里仅用最后访问(读)时间为例:
#include <sys/unistd.h> #include <sys/stat.h> /** /brief 判断文件(文件夹)的最后访问时间 * * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径 * /return time_t * >0:成功; * 0:错误; */ time_t getReadTime(const char* _path) { struct stat buff; if(stat(_path,&buff) == 0) { return buff.st_atime; } return 0; }
另外两种时间的获取方式,就当作小练习吧。
最后来谈谈如何根据st_mode来判断文件(文件夹)的类型,这里可以利用库本身就定义好的一些宏:
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) /**文件夹的判断*/
#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) /**普通文件的判断*/
实例如下:
#include <sys/unistd.h> #include <sys/stat.h> #include <sys/types.h> /** /brief 判断文件(文件夹)的类型 * * /param const char* _path: 文件或文件夹的路径,可以为绝对路径或相对路径 * /return signed char * 0:普通文件 * 1:文件夹 * 2:管道文件 * 3:字符设备文件 * 4:块设备文件 * -1:错误,错误号可以从全局的errno获取; */ signed char getFileType(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; } }
当然在项目中一般是不用硬编码的,可以定义相关的enum。
***********************************************************************************************************************************************************
万事开头难,一开始学linux编程,确实很难摸到门径,但是我相信只要找到合适的方法和不懈的努力,应该能学好的!自己给自己加油!
仓促之作,一定会漏洞百出,请大侠们多多批评指教。。。哈哈,拜谢了!!!
所有实例均在gcc 4.1.2,redhat 企业版 5.3下编译测试通过,环境参数如下:
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=i386-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)