5. 文件属性和目录
- 1. Linux 系统的文件类型
- 1.1 普通文件
- 1.2 目录文件
- 1.3 字符设备文件和块设备文件
- 1.4 符号链接文件
- 1.5 管道文件
- 1.6 套接字文件
- 2. stat 系统调用
- 2.1 struct stat 结构体
- 2.2 st_mode 变量
- 2.3 struct timespec 结构体
- 3. fstat 和 lstat 函数
- 3.1 fstat 函数
- 3.2 lstat 函数
- 4. 文件属主
- 4.1 有效用户ID和有效组ID
- 4.2 chown 函数
- 5. 文件访问权限
- 5.1 普通权限和特殊权限
- 5.2 目录权限
- 5.3 检查文件权限 access
- 5.4 修改文件权限
- 5.5 umask 函数
- 6. 文件的时间属性
- 6.1 utime()、utimes() 修改时间属性
- 6.1.1 utime()
- 6.1.2 utimes()
- 6.2 futimens()、utimensat() 修改时间属性
- 6.2.1 futimens()
- 6.2.1 utimensat()
- 7. 符号链接(软链接)与硬链接
- 7.1 创建链接文件
- 7.1.1 创建硬链接
- 7.1.2 创建软链接
- 7.2 读取软链接文件
- 8. 目录
- 8.1 目录存储形式
- 8.2 创建和删除目录
- 8.3 打开、读取以及关闭目录
- 8.4 进程的当前工作目录
- 9. 删除文件
- 10. 文件重命名
1. Linux 系统的文件类型
Linux 系统不以后缀区分文件类型,但是建议自己命名时遵循 window 系统命名规则。Linux 系统下一共分为 7 种文件类型
1.1 普通文件
普通文件中的数据存在系统磁盘中,可以访问文件中的内容,文件中的内容以字节为单位进行访问。普通文件可分为文本文件和二进制文件
文本文件: 文件中的内容都是由文本构成的,所谓文本指的是 ASCII 码字符。文件中的内容其本质上都是数字,因为计算机本身只有 0 和 1,这些数字被理解为字符
二进制文件: 这些数字都是真正的数字
在 Linux 系统中,可以通过stat
命令或者ls
命令查看文件类型。通过ls
查看时,是通过字符显示的。
1.2 目录文件
目录文件就是文件夹
1.3 字符设备文件和块设备文件
设备文件对应的是硬件设备,应用程序通过对设备文件的读写来操控硬件设备。设备文件不存在于磁盘中,而是由文件系统虚拟出来的,一般是由内存来维护的,当系统关机时,设备文件都会消失。 设备文件一般存放在 Linux 系统 /dev/ 目录下,所以 /dev 也称为虚拟文件系统 devfs。
1.4 符号链接文件
类似于 window 中的快捷方式文件,它的内容指向的是另一个文件路径,当对符号链接文件进行操作时,会转移到它指向的文件上去,而不是对它本身进行操作。
1.5 管道文件
管道文件主要用于进程间通信,后续再讲解
1.6 套接字文件
套接字文件也是一种进程间通信的方式,与管道文件不同的是,它们可以在不同主机上的进程间通信,也就是网络通信,后续做详细讲解
2. stat 系统调用
可以通过这个命令查看文件的属性
#include <sys/type.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
// buf指针指向的是一个stat结构体变量,获取到的文件属性信息就记录在该结构体中
// 成功返回0,失败返回-1,并设置error
2.1 struct stat 结构体
struct stat
{
dev_t st_dev; /* 文件所在设备的 ID */
ino_t st_ino; /* 文件对应 inode 节点编号 */
mode_t st_mode; /* 文件对应的模式 */
nlink_t st_nlink; /* 文件的链接数 */
uid_t st_uid; /* 文件所有者的用户 ID */
gid_t st_gid; /* 文件所有者的组 ID */
dev_t st_rdev; /* 设备号(指针对设备文件) */
off_t st_size; /* 文件大小(以字节为单位) */
blksize_t st_blksize; /* 文件内容存储的块大小 */
blkcnt_t st_blocks; /* 文件内容所占块数 */
struct timespec st_atim; /* 文件最后被访问的时间 */
struct timespec st_mtim; /* 文件内容最后被修改的时间 */
struct timespec st_ctim; /* 文件状态最后被改变的时间 */
};
2.2 st_mode 变量
是一个 32 位无符号整形数据,记录了文件类型和权限
和 open 函数的 mode 参数区别的地方就是 st_mode 可以设置文件类型
判断文件类型方式
// 使用 S_IFMT 文件类型字段位掩码
if((st.st_mode & S_IFMT) == S_IFREG) // 判断是不是普通文件
{}
// 使用 Linux 封装好的宏来判断
if(S_ISREG(st.st_mode)){} // 判断是不是普通文件,是返回true,不是返回false
if(S_ISDIR(st.st_mode)){} // 判断是不是目录文件,是返回true,不是返回false
if(S_ISCHR(st.st_mode)){} // 判断是不是字符设备文件,是返回true,不是返回false
if(S_ISBLK(st.st_mode)){} // 判断是不是块设备文件,是返回true,不是返回false
if(S_ISFIFO(st.st_mode)){} // 判断是不是管道文件,是返回true,不是返回false
if(S_ISLNK(st.st_mode)){} // 判断是不是链接文件,是返回true,不是返回false
if(S_ISSOCK(st.st_mode)){} // 判断是不是套接字文件,是返回true,不是返回false
2.3 struct timespec 结构体
该结构体是系统中时间相关的结构体
struct timespec
{
time_t tv_sec; // 秒
syscall_slong_t tv_nsec; // 纳秒
};
在 Linux 系统中,time_t 指的是一个时间段,从某一个时间点到某一个时间点所经过的秒数。
3. fstat 和 lstat 函数
这两个函数也是用来获取文件属性信息的,但是参数有些不同
3.1 fstat 函数
是通过文件描述符获取文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int fstat(int fd, struct stat *buf);
3.2 lstat 函数
对于符号链接文件,stat、fstat 查阅的是符号链接文件所指向的文件对应的属性信息,而 lstat 函数查阅的是符号链接文件本身的属性
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int lstat(const char *pathname, struct stat *buf);
4. 文件属主
Linux 系统中存在多个不同的用户,而系统中每一个文件都是有一个与之相关的用户和用户组。登录的是谁的账号,所有者就是谁,通过 ID 识别不同的用户和用户组,将用户名或用户组名与之对应的 ID 关联起来。用户 ID 是 UID,组 ID 是 GID。而与一个进程相关的 ID 有 5 个,
4.1 有效用户ID和有效组ID
有效用户ID和有效组ID 是站在操作系统的角度,用于给操作系统判断当前执行该进程的用户在当前环境下对某个文件是否有相应的权限。当进程在进行权限检查的时候,并不是通过进程的实际用户和实际组来判断的,而是通过有效用户和有效组。 通常,进程的有效用户等于实际用户,有效组等于实际组
4.2 chown 函数
改变文件的所有者和所属组。
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
// 后两个参数都是需要修改为谁的
只有超级用户进程才能更改文件的用户ID和组ID,普通用户可以将文件的组ID修改为其所从属的任意附属组ID,前提是该进程的有效用户ID 等于文件的用户ID。
5. 文件访问权限
所有文件类型都有访问权限,并不是仅仅指普通文件
5.1 普通权限和特殊权限
普通权限是文件所有者、所属组和其他用户对文件的读写以及执行权限。特殊权限则是S字段的权限,由高到低依次是 set-user-ID 位权限、set-group-ID 位权限以及 sticky 位权限,分别使用 S_ISUID(04000)、S_ISGID(02000) 和 S_ISVTX(01000) 表示。
当进程对文件进行操作的时候,将进行权限检查,如果 set-user-ID 位权限被设置,内核会将进程的有效 ID 设置为该文件的所有者 ID,意味着该进程直接获取了文件所有者权限。如果 set-group-ID 位权限被设置,就会将用户组 ID 设置为所属组 ID,获取了所属组权限。
5.2 目录权限
- 目录的读权限:可以使用
ls
等命令,列出目录下的内容 - 目录的写权限:可以在目录下创建、删除文件
- 目录的执行权限:可访问目录下的文件,对目录下的文件进行读写或执行
5.3 检查文件权限 access
程序当中对文件进行相关操作之前,需要先检查权限
#include <unistd.h>
int access(const char *pathname, int mode);
/* mode:
* F_OK: 检查文件是否存在
* R_OK: 检查是否有读权限
* W_OK: 检查是否有写权限
* X_OK: 检查是否有执行权限
* 不仅可以单独使用,还可以使用 | 连接
* /
// 通过返回0,没有某权限就返回-1,如果有多个检查,只要有一个不符合就不会通过
5.4 修改文件权限
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
// 若pathname指向软链接文件,则实际修改的是链接文件指向的文件
// mode就是权限
int fchmod(int fd, mode_t mode);
// 使用fd访问文件
5.5 umask 函数
Linux 中有一个 umask 命令,可以查看/设置权限掩码,权限掩码主要用于对新建文件的权限进行屏蔽。权限掩码的表示方式和文件权限的表示方式相同,**但是要去除特殊权限位,umask 不能对特殊权限位进行屏蔽。**当新建文件时,实际权限不等于设置的权限,应该是mode & ~umask
。umask 是进程自身的一种属性,也就是两个不相关进程之间的umask没有关闭,除非是父子进程这种,子进程继承父进程的 umask
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
// 返回旧的 umask
6. 文件的时间属性
文件有 3 个时间属性,分别是文件最后被访问的时间、文件内容最后被修改的时间以及文件状态最后被改变的时间,分别记录在 struct stat 结构体的 st_atim、st_mtim 和 st_ctim 变量中。
文件最后被访问时间指的是最后一次读取文件内容的时间,比如使用 read 函数;文件内容最后被修改的时间就是内容发生了改变,比如使用 write 函数;文件状态最后被改变的时间,是指文件的 inode 节点最后一次最修改的时间,譬如更改文件的访问权限、更改文件用户 ID、更改链接数等。
6.1 utime()、utimes() 修改时间属性
注意: 只能显示修改文件访问时间和修改时间,不能显示修改状态改变时间。
只有三种情况可以修改时间属性:超级用户进程、有效用户与文件所有者相匹配的进程以及参数times为空时对文件有写权限的进程。
6.1.1 utime()
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
// times:如果设置为NULL,则表示修改为系统当前时间
// 成功返回0,失败返回-1
struct utimbuf 结构体
struct utimbuf{ time_t actime; // 访问时间,其实就是 long int 类型 time_t modtime; // 内容修改时间,都是以秒为单位 };
6.1.2 utimes()
#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);
// times:数组第一个元素用于指定访问时间,第二个用于指定内容修改时间
struct timeval{
long tv_sec; // 秒
long tv_usec; // 微秒
};
6.2 futimens()、utimensat() 修改时间属性
用于显示修改时间戳,但是这两个函数的优点是:
- 可以按纳秒级精度设置时间
- 可单独设置某一时间戳,而如果使用前两个函数,就需要先使用 stat 获取当前时间戳
- 可独立将任一时间设置为当前时间,而前两个函数将 times 设置为 NULL 就会将所有时间戳修改
6.2.1 futimens()
#include <fcntl.h>
#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);
/* times:
* 如果是 NULL,就表示将访问时间和修改时间都设置为当前时间
* 如果任一数组元素的 tv_nsec 设置为 UTIME_NOW,就表示相应的时间戳设为当前时间,会自动忽略tv_sec
* 如果任一数组元素的 tv_nsec 设置为 UTIME_OMIT,表示相应的时间戳不变,忽略tv_sec
* 如果都不是,就设置为指定的值
* /
6.2.1 utimensat()
#include <fcntl.h>
#include <sys/stat.h>
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
/* dirfd:可以是一个目录的文件描述符,也可以是特殊值 AT_FDCWD; 如果 pathname 指定的是绝对路径,此参数会被忽略
* pathname:如果是一个相对路径,并且dirfd不为AT_FDCWD, 则对dirfd指向的目录进行解析;如果是相对路径,并且dirfd为AT_FDCWD,则对相对于当前进程的当前目录进行操作
* flags:可以为0,也可以设置为 AT_SYMLINK_NOFOLLOW,当为特殊值时,当pathname指定的文件时符号链接,则修改的是该符号链接的时间戳
* /
// 成功返回0,失败返回-1
7. 符号链接(软链接)与硬链接
使用ln
指令可以设置链接文件:
- 硬链接:
ln 源文件名 链接文件名
- 软链接:
ln -s 源文件名 链接文件名
硬链接文件与源文件具有相同的 inode,也就指向的是物理磁盘的同一块,只是文件名不同。inode 会记录文件的链接数,就是指硬链接数,包括源文件本身,当链接数减为0的时候,也就意味着文件被删除了。ls -li
可以查看链接数。
软链接的数据块中存储的是源文件的路径名,当源文件被删除之后,软链接依旧存在,但是指向的是一个无效的文件路径,被称为悬空链接,而且软链接不计入链接数中。
但是对于硬链接有一些限制情况:
- 不能对目录创建硬链接,只有超级用户才能,但是必须在底层文件系统支持的情况下
- 硬链接通常要求链接文件和源文件位于同一文件系统中
软链接的优点: - 可以对目录创建软链接
- 可以跨越不同文件系统
- 可以对不存在的文件创建软链接
7.1 创建链接文件
7.1.1 创建硬链接
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
/* oldpath:需要创建硬链接的文件路径,避免指向软链接文件,因为对软链接文件创建硬链接没意义,虽然不报错
* newpath:硬链接文件路径,如果已存在,就会报错
* /
// 成功返回0,失败返回-1
7.1.2 创建软链接
#include <unistd.h>
int symlink(const char *target, const char *linkpath);
/* target:源文件路径,可以是一个软链接文件,如果指定文件不存在,就是悬空链接
* linkpath:指定软链接文件路径,如果存在就报错
* /
// 成功返回0,失败返回-1
7.2 读取软链接文件
#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
/* pathname:需要读取的软链接文件地址,只能是软链接文件,不能为其他文件,否则报错
* buf:用于存放路径信息的缓冲区
* bufsiz:读取大小,一般需要大于链接文件数据块中存储的文件路径信息大小
* /
// 成功返回读取到的字节数,失败返回-1
8. 目录
目录文件是一种特殊文件,不适合使用前面介绍的文件 IO 操作,Linux 中有专门的对目录文件操作的函数
8.1 目录存储形式
常规文件包括了 inode 节点以及文件内容数据存储块,但是对于目录文件,其存储形式是由 inode 节点和目录块构成,目录块中记录了哪些文件组织在这个目录下,记录它们的文件名以及对应的 inode 编号
8.2 创建和删除目录
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathtime, mode_t mode);
// 如果路径名已存在,就报错,成功返回0,失败返回-1
#include <unistd.h>
int rmdir(const char *pathname);
// 需要删除的目录必须是一个空目录,也就是只能有 .. 和 . 这两个文件,并且不能是软链接文件
// 成功返回0,失败返回-1
8.3 打开、读取以及关闭目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
// 返回一个结构体指针,如果失败返回NULL
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
// 返回值表示dirp指向的目录流中下一个目录条目,在到达目录流的末尾或发生错误时,返回NULL
struct dirent{
ino_t d_ino; /* inode 编号 */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all filesystem types */
char d_name[256]; /* 文件名 */
};
// 每调用一次该函数,返回值都会被覆盖,因为该结构体指针式静态分配的
#include <sys/types.h>
#include <dirent.h>
void rewinddir(DIR *dirp);
// 该函数可以重置为目录流起点,以便下一个从第一个文件开始读取
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
8.4 进程的当前工作目录
#include <unistd.h>
char *getcwd(char *buf, size_t size);
// buf:存放get到的地址,size必须足够大
// 如果buf为空,且size为0,就会按需分配一个缓冲区,但是调用者使用完后,必须free这一块空间
int chdir(const char *path);
int fchdir(int fd);
// 这两个函数是用来改变当前工作目录
9. 删除文件
这里的删除文件,是指删除普通文件
#include <unistd.h>
int unlink(const char *pathname);
// 如果指定文件不存在,就调用失败
该函数用于删除一个硬链接,如果硬链接数不为0,那么其他硬链接可以访问该文件的数据,只有当为0的时候,才会删除数据内容
#include <stdio.h>
int remove(const char *pathname);
// 如果是一个非目录文件路径,就会自动调用unlink,如果是目录文件,就会调用rmdir
// 该函数不对软链接进行解引用操作,而是会删除链接文件本身,不是所指向的源文件
10. 文件重命名
#include <stdio.h>
int rename(const char *oldpath,const char *newpath);
调用 rename()会将现有的一个路径名 oldpath 重命名为 newpath 参数所指定的路径名。 rename()调用仅操作目录条目,而不移动文件数据(不改变文件 inode 编号、不移动文件数据块中存储的内容),重命名既不影响指向该文件的其它硬链接,也不影响已经打开该文件的进程(譬如,在重命名之前该文件已被其它进程打开了,而且还未被关闭)。根据 oldpath、newpath 的不同,有以下不同的情况需要进行说明:
- 若 newpath 参数指定的文件或目录已经存在,则将其覆盖;
- 若 newpath 和 oldpath 指向同一个文件,则不发生变化(且调用成功)。
- rename()系统调用对其两个参数中的软链接均不进行解引用。如果 oldpath 是一个软链接,那么将重命名该软链接;如果 newpath 是一个软链接,则会将其移除、被覆盖。
- 如果 oldpath 指代文件,而非目录,那么就不能将 newpath 指定为一个目录的路径名。要想重命名一个文件到某一个目录下,newpath 必须包含新的文件名。
- 如果 oldpath 指代为一个目录,在这种情况下,newpath 要么不存在,要么必须指定为一个空目录。
- oldpath 和 newpath 所指代的文件必须位于同一文件系统。由前面的介绍,可以得出此结论!
- 不能对.(当前目录)和…(上一级目录)进行重命名