《Linux编程》学习笔记 ·004【文件I/O操作】

注:前言、目录见 https://blog.csdn.net/qq_44220418/article/details/108428971

一、文件系统简介

1、索引节点

Linux系统采用按名存取的方式访问文件

除了文件名以外,文件的主要属性信息都存放在inode节点

目录文件中保存着文件名和索引节点的对应关系【可用命令ls -i查看文件/目录的inode号】


Linux系统中文件包括两部分

  • 索引节点inode
    \qquad 记录文件属性信息(除了文件名)【可用命令ls -l查看】
       \;
    \qquad 文件属性包括 { 文 件 类 型 访 问 权 限 文 件 主 人 组 长 度 访 问 日 期 … … \begin{cases} 文件类型 \\ 访问权限 \\ 文件主人 \\ 组 \\ 长度 \\ 访问日期 \\ …… \end{cases} 访访
       \;
    \qquad 索引节点的结构如下图所示:
    \qquad\qquad
  • 数据块
    \qquad 存放文件具体内容

注:硬链接文件的inode与源文件的inode相同

更多关于inode节点、软链接与硬链接的区别相关的知识可以参考博文 Linux文件索引节点inode

2、文件系统组成

一个简单的Unix文件系统组成
\qquad
\qquad 引导块:用于引导该分区内操作系统的引导程序
\qquad 超级块:UFS的重要参数,如文件系统的 { 块 索 引 节 点 总 数 空 闲 块 空 闲 索 引 节 点 数 \begin{cases} 块 \\ 索引节点总数 \\ 空闲块 \\ 空闲索引节点数 \\ \end{cases}
\qquad 索引节点表(i节点):文件系统中索引节点的集合
\qquad 数据块:存储文件数据


Linux文件系统——Ext2
\qquad
\qquad 第一个是引导块,其他空间分成各个块组
\qquad 每个块组 { 超 级 块 块 组 描 述 符 , 数 据 块 位 图 , 索 引 节 点 位 图 , 索 引 节 点 , 数 据 块 \begin{cases} 超级块 \\ 块组描述符,数据块位图,索引节点位图,索引节点,数据块 \end{cases} {

3、文件

(1).类型

文件类型 { 普 通 文 件 ( rugular file ) 目 录 文 件 ( directory ) 字 符 设 备 文 件 ( character device ) 块 设 备 文 件 ( block device ) FIFO 文 件 ( fifo ) 符 号 链 接 文 件 ( symbolic link ) socket 套 接 字 文 件 ( socket ) \begin{cases} 普通文件 & (\text{rugular file})\\ 目录文件 & (\text{directory})\\ 字符设备文件 & (\text{character device})\\ 块设备文件 & (\text{block device})\\ \text{FIFO}文件 & (\text{fifo})\\ 符号链接文件 & (\text{symbolic link})\\ \text{socket}套接字文件 & (\text{socket})\\ \end{cases} FIFOsocketrugular filedirectorycharacter deviceblock devicefifosymbolic linksocket

(2).文件权限

[1].文件权限

Linux中的用户分为三类 { user/owner 文 件 拥 有 者 group 用 户 组 other 其 他 用 户 \begin{cases} \text{user/owner} & 文件拥有者 \\ \text{group} & 用户组 \\ \text{other} & 其他用户 \\ \end{cases} user/ownergroupother

Linux文件对三类用户都有三种访问权限 { r 可 读 w 可 写 x 可 执 行 \begin{cases} \text{r} & 可读 \\ \text{w} & 可写 \\ \text{x} & 可执行 \\ \end{cases} rwx

另外,Linux文件还有三种特殊的权限 { setuid setgid sticky \begin{cases} \text{setuid} \\ \text{setgid} \\ \text{sticky} \\ \end{cases} setuidsetgidsticky
这里就不对这三种特殊权限做具体介绍了,想知道更详细的可以去看博文 Linux文件特殊权限——SetUID、SetGID、Sticky BIT


Linux文件的索引节点里面有一个st_mode字段,记录文件类型及权限
该字段共 16 16 16 { 高 4 位 文 件 类 型 低 12 位 文 件 权 限 \begin{cases} 高4位 & 文件类型 \\ 低12位 & 文件权限 \\ \end{cases} {412

12位文件权限 { 特 殊 权 限 { setuid setgid sticky 文 件 拥 有 者 { r 可 读 权 限 w 可 写 权 限 x 可 执 行 权 限 用 户 组 { r 可 读 权 限 w 可 写 权 限 x 可 执 行 权 限 其 他 用 户 { r 可 读 权 限 w 可 写 权 限 x 可 执 行 权 限 \begin{cases} 特殊权限 & \begin{cases} \text{setuid} \\ \text{setgid} \\ \text{sticky} \\ \end{cases} \\ \\ 文件拥有者 & \begin{cases} \text{r} & 可读权限 \\ \text{w} & 可写权限 \\ \text{x} & 可执行权限 \\ \end{cases} \\ \\ 用户组 & \begin{cases} \text{r} & 可读权限 \\ \text{w} & 可写权限 \\ \text{x} & 可执行权限 \\ \end{cases} \\ \\ 其他用户 & \begin{cases} \text{r} & 可读权限 \\ \text{w} & 可写权限 \\ \text{x} & 可执行权限 \\ \end{cases} \\ \end{cases} setuidsetgidstickyrwxrwxrwx


如下图所示:

在这里插入图片描述

[2].表示方法

文件权限的表示方法

  • 字母
    字母权限
    r可读
    w可写
    x可执行
    -
    如:rwxr-xr-x就表示了 { 拥 有 者 对 文 件 可 读 、 可 写 、 可 执 行 用 户 组 对 文 件 可 读 、 可 执 行 其 他 用 户 对 文 件 可 读 、 可 执 行 \begin{cases} 拥有者对文件可读、可写、可执行 \\ 用户组对文件可读、可执行 \\ 其他用户对文件可读、可执行 \\ \end{cases}
  • 数字(9位二进制 / 3位八进制)
    和上面的字母类似,对应位的二进制 { 1 有 权 限 0 无 权 限 \begin{cases} 1 & 有权限 \\ 0 & 无权限 \\ \end{cases} {10
    如:111 101 101就相当于rwxr-xr-x755就相当于rwxr-xr-x
  • 宏(S_IPwww模式)
    S_I固定,P可为RWXwww可为USRGRPOTH,多种权限可用或运算连接
    如:S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH就相当于rwxr-xr-x

注:编程中作为参数时,只能使用数字/宏

(3).访问文件

每打开一次文件

  • 系统增加一个file表项
  • 进程增加一个fd表项(以文件描述符为下标),通过指针指向file表项
  • 用户通过文件描述符系统调用访问该文件

二、文件基本I/O操作

1、文件描述符

用户空间中任何打开的文件都被分配一个唯一的非负整数,标识该打开文件,即文件描述符

进程默认打开的三个文件描述符

  • 标准输入 0
  • 标准输出 1
  • 标准错误输出 2

会选用文件描述符中未使用的最小值

2、常用系统调用

常用的系统调用如下

系统调用说明
open打开/新建文件
close关闭文件
creat新建文件
read读文件
write写文件
lseek定位文件偏移量

(1).open

定义在<fcntl.h>

函数原型如下:

int open( const char *path, int flags);
int open( const char *path, int flags, mode_t perms);
功能
打开一个已经存在的文件时,参数 perms可以省略
创建并打开一个不存在的文件时,参数 perms必填
参数
参数说明
path文件路径
flags文件打开方式(可用或运算|连接多种打开方式)
关于flags可选的宏参数可参考博客 https://blog.csdn.net/ArchyLi/article/details/78937937
perms文件权限(4位八进制,仅创建时需要)
返回值
成功:返回最小可用的文件描述符(当前文件所用的文件描述符)
失败:返回 -1(置 errno

(2).close

定义在<unistd.h>

函数原型如下:

int close(int fd );
功能
关闭文件,释放文件描述符,使之可再利用
参数
参数说明
fd文件描述符
返回值
成功:返回 0
失败:返回 -1(置 errno

注:当一个进程终止时,内核会自动检查并回收该进程所有的文件描述符

(3).write

定义在<unistd.h>

函数原型如下:

ssize_t write(int fd, const void *buf, size_t nbytes);
功能
buf所指缓冲区中的 nbytes个字节写入文件描述符 fd所指示的已打开文件中
注意点
写操作从当前文件偏移量处开始写数据,写完后,文件偏移量也将后移成功写入的字节数(因此不移动读写指针读不到刚刚写入的内容)
若调用open打开文件时,读写指针初始指向文件开头
\qquad flags包含O_APPEND,则每次写入前,读写指针都将移动到文件末尾
由于物理介质空间不足等原因将会使得write的返回值小于计划要写入文件的字节个数
参数
参数说明
fd文件描述符
buf要写入的数据
nbytes要写入数据的字节数
返回值
成功:返回已写入的字节数
失败:返回 -1(置 errno

(4).read

定义在<unistd.h>

函数原型如下:

ssize_t read(int fd, void *buf, size_t nbytes);
功能
从一文件描述符 fd对应的已打开文件中,读取 nbytes字节的数据放入 buf
注意点
读操作从当前文件偏移量处开始读数据,读完后,当前文件偏移也将改变成功读出的字节个数
若调用open打开文件时,读写指针初始指向文件开头
有时读到的数据可能比希望读的字节少,但系统不会置errno,因为这不是错误(如文件已经到结尾),需要用户自己去推测问题所在
参数
参数说明
fd文件描述符
buf保存读取数据的缓冲区
nbytes要读取数据的字节数
返回值
成功:返回已读取的字节数
失败:返回 -1(置 errno

(5).lseek

定义在<unistd.h>

函数原型如下:

off_t lseek(int fd, off_t pos, int whence);
文件偏移量
文件偏移量是一个非负整数,它用于标识下一次读或写文件的位置
除了O_APPEND模式打开文件外,读/写操作都从当前文件偏移量处开始
open一次文件,就能得到一个新的打开文件表项,因此每次open都可以得到一个独立的文件偏移量
功能
移动文件读写指针(文件偏移量)
参数
参数说明
fd文件描述符
pos相对于whence的位置的偏移量
whenceSEEK_SET:代表文件开始,即设文件偏移量为pos
SEEK_CUR:代表当前位置,即设文件偏移量为当前值+pos
SEEK_END:代表文件结尾,即设文件偏移量为文件长度+pos
返回值
成功:返回当前文件偏移量
失败:返回 -1(置 errno

(6).使用示例

示例1:创建文件权限为rw-r--r--的文件test.txt(初始时不存在),在其末尾写入内容

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

int main()
{
	// 创建并打开文件test.txt
	int fd = open("test.txt", O_RDWR | O_APPEND | O_CREAT, 0644);
	if (fd == -1)
	{
		perror("open");
		exit(1);
	}

	// 写入文件
	char str1[] = "123456789012345678901234567890\n";
	if (write(fd, str1, strlen(str1)) != -1)
	{
		printf("写入成功\n");
	}

	// 关闭文件
	close(fd);

	return 0;
}

程序输出的内容如下

写入成功

生成的test.txt文件内容如下

123456789012345678901234567890

示例2:从刚刚 示例1 生成的test.txt中读取至多 99 99 99 字节的数据

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

int main()
{
	// 打开文件
	int fd = open("test.txt", O_RDWR);
	if (fd == -1)
	{
		perror("open");
		exit(1);
	}
	// 读取文件
	char buf[100];
	read(fd, buf, sizeof(buf) - 1);
	printf("读取到的字符串是\n%s\n", buf);
	// 关闭文件
	close(fd);

	return 0;
}

示例3:创建文件权限为rw-r--r--的文件test3.txt(初始时不存在),利用lseek函数定位,写入、读取文件内容

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

int main()
{
	// 创建
	int fd3 = open("test3.txt", O_RDWR | O_CREAT, 0644);
	char str2[] = "newnewnewnewnewnewnewnew\n";
	char str3[] = "test\n";
	char buf[100];
	
	// 向文件末尾写入数据
	lseek(fd3, 0, SEEK_END);
	write(fd3, str2, strlen(str2));

	// 从文件开头读取数据
	lseek(fd3, 0, SEEK_SET);
	read(fd3, buf, sizeof(buf) - 1);
	printf("读取到的字符串3是\n%s\n", buf);

	// 向文件开头写入数据
	lseek(fd3, 0, SEEK_SET);
	write(fd3, str3, strlen(str3));

	// 从文件开头读取数据
	lseek(fd3, 0, SEEK_SET);
	read(fd3, buf, sizeof(buf) - 1);
	printf("读取到的字符串4是\n%s\n", buf);

	close(fd3);

	return 0;
}

程序输出的内容如下

读取到的字符串3是
newnewnewnewnewnewnewnew

读取到的字符串4是
test
wnewnewnewnewnewnew

生成的test3.txt文件内容如下

test
wnewnewnewnewnewnew

显然这个最终结果是将test3.txt之前的前5个字符"newne"修改成了"test\n"


(7).探索与练习

探索1:对同一文件的不同fd的操作是否会造成相互影响?

已存在test2.txt,内容如下:

1234321222222

编写了如下C源程序对其进行探究:

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

int main()
{
	char str1[] = "123456789012345678901234567890\n";
	char buf[100];
	int fd1 = open("test2.txt", O_RDWR | O_APPEND);
	int fd2 = open("test2.txt", O_RDONLY);
	write(fd1, str1, strlen(str1));
	write(fd1, str1, strlen(str1));
	write(fd1, str1, strlen(str1));
	read(fd2, buf, sizeof(buf) - 1);
	printf("读取到的字符串2是\n%s\n", buf);
	close(fd1);
	close(fd2);

	return 0;
}

程序输出的内容如下

读取到的字符串2是
1234321222222

123456789012345678901234567890
123456789012345678901234567890
1234567890123456789012

修改后的test2.txt文件内容如下

1234321222222

123456789012345678901234567890
123456789012345678901234567890
123456789012345678901234567890

显然,fd1fd2都是已打开文件test2.txt的文件描述符

fd1的写操作改变了fd1的文件偏移量,而没有改变fd2的文件偏移量(从读操作正常可见)

这也就验证了 每次open都可以得到一个独立的文件偏移量


练习1:将键盘输入的一批字符串(以quit结束)写入到文件test.txt中,然后再读出并显示到屏幕上

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

int main()
{
	char str[999];
	char buf[100];

	// 打开文件
	int fd = open("test.txt", O_CREAT | O_RDWR, 0644);

	// 循环读入并写入
	scanf("%s", &str);
	while (strcmp(str, "quit"))
	{
		// 写入
		write(fd, str, strlen(str));
		scanf("%s", &str);
	}

	// 将文件偏移量定位到文件开头
	lseek(fd, 0, SEEK_SET);

	// 循环读取文件内容
	printf("读取到的写入内容如下:\n");
	while (read(fd, buf, sizeof(buf) - 1))
	{
		printf("%s", buf);
	}
	printf("\n");

	// 关闭文件
	close(fd);

	return 0;
}

程序输入的内容如下

1234
abc
quit

程序输出的内容如下

读取到的写入内容如下:
1234abc

创建的test.txt文件内容如下

1234abc

三、目录操作

1、目录文件

Linux系统中,目录也是以文件的方式存储,在目录文件中存储了该目录下的文件的信息,包括 { 文 件 名 文 件 i n o d e 编 号 文 件 类 型 ⋯ \begin{cases} 文件名 \\ 文件inode编号 \\ 文件类型 \\ \cdots \\ \end{cases} inode,姑且把这些信息称为一个文件的索引信息

如果想要找到某个文件,首先需要找到这个文件所在目录的目录文件

2、常用系统调用

常用的系统调用如下

系统调用说明
opendir打开目录
readdir读取目录
telldir返回当前打开的目录的下次读取位置
seekdir设置目录流的读取位置为指定位置
rewinddir重置目录流的读取位置为起始位置
closedir关闭目录

(1).opendir

定义在<dirent.h>

函数原型如下:

DIR * opendir(const char *dir_path);
功能
打开一个目录,返回一个 DIR指针,从而创建一个到目录的连接
参数
参数说明
dir_path目录路径
返回值
成功:返回指向参数目录的指针
失败:返回 NULL(置 errno

(2).readdir

定义在<dirent.h>

函数原型如下:

struct dirent * readdir(DIR *dir);
功能
DIR指针中读取一个目录项(文件/目录)的信息,并后移指针对应目录的读取位置
参数
参数说明
dir已打开目录对应的DIR指针
返回值
成功:返回相应目录项的结构体指针
失败:返回 NULL(目录项读取完成)

(3).closedir

定义在<dirent.h>

函数原型如下:

int closedir(DIR * dir);
功能
关闭目录
参数
参数说明
dir已打开目录对应的DIR指针
返回值
成功:返回 0
失败:返回 -1(置 errno

(4).rewinddir

定义在<dirent.h>

函数原型如下:

void rewinddir(DIR * dir);
功能
重置目录指针读取位置到起始位置
参数
参数说明
dir已打开目录对应的DIR指针

(5).seekdir

定义在<dirent.h>

函数原型如下:

void seekdir(DIR * dir, off_t offset);
功能
设置目录指针读取位置到指定位置
参数
参数说明
dir已打开目录对应的DIR指针

(6).telldir

定义在<dirent.h>

函数原型如下:

off_t telldir(DIR * dir);
功能
返回目录指针下次读取的位置(可以理解为一个从 0开始的索引号)
参数
参数说明
dir已打开目录对应的DIR指针
返回值
成功:返回当前目录指针下次读取的位置
失败:返回 -1(置 errno

(7).mkdir

定义在<sys/stat.h>

函数原型如下:

int mkdir(char * pathname, mode_t mode);
功能
创建目录,并指定其权限模式
参数
参数说明
pathname创建目录的路径
mode创建目录的权限模式
Tips:关于mode的选项,可以参考博客 mkdir()函数、mode_t参数
返回值
成功:返回 0
失败:返回 -1(置 errno

(8).rmdir

定义在<unistd.h>

函数原型如下:

int rmdir(char * pathname);
功能
删除目录
参数
参数说明
pathname要删除目录的路径
返回值
成功:返回 0
失败:返回 -1(置 errno

(9).getcwd

定义在<unistd.h>

函数原型如下:

char* getcwd(char *buf, size_t size);
功能
获取进程当前所处的工作目录的绝对路径,存入参数字符数组中
参数
参数说明
buf存储路径的字符数组
sizebuf所指向字符数组的字节数
返回值
成功:返回存储空间的首地址
失败:返回 NULL(置 errno

(10).chdir

定义在<unistd.h>

函数原型如下:

int chdir(const char *pathname);
功能
切换当前进程所处的工作目录
注意点
对其他进程、调用该进程的进程的当前工作目录均没有影响
参数
参数说明
pathname要切换到的工作目录路径
返回值
成功:返回 0
失败:返回 -1(置 errno

(11).rename

定义在<unistd.h>

函数原型如下:

int rename(const char *from, const char *to);
功能
移动并重命名文件
注意点
rename并不真正复制文件中的数据,它只是将文件的链接从一个目录移动到另一个目录中,这个过程数据本身并没有移动
参数
参数说明
from源文件路径
to新文件路径
返回值
成功:返回 0
失败:返回 -1(置 errno

(12).使用示例

示例4:尝试使用上述若干系统调用读取、创建、删除、更改、查看目录

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

int main()
{
	// 打开目录testdir
	DIR * dir_ptr = opendir("testdir");
	struct dirent * dir;

	// 获取下次读取位置
	if (dir_ptr != NULL)
		printf("下次读取的位置是:%ld\n\n", telldir(dir_ptr));

	// 获取目录下所有文件的名称
	printf("第一次打印:\n");
	if (dir_ptr != NULL)
		while ((dir = readdir(dir_ptr)) != NULL)
			printf("%s\n", dir->d_name);
	printf("\n");

	// 重定位,重新读取
	printf("第二次打印:\n");
	if (dir_ptr != NULL)
	{
		rewinddir(dir_ptr);
		while ((dir = readdir(dir_ptr)) != NULL)
			printf("%s\n", dir->d_name);
	}
	printf("\n");

	// 重定位,重新读取
	printf("第三次打印:\n");
	if (dir_ptr != NULL)
	{
		seekdir(dir_ptr, 3);
		while ((dir = readdir(dir_ptr)) != NULL)
			printf("%s\n", dir->d_name);
	}
	printf("\n");

	// 关闭目录
	closedir(dir_ptr);

	// 创建目录
	int res = mkdir("./testmk", 0777);
	if (!res)
		printf("创建目录成功\n\n");

	// 删除目录
	res = rmdir("./testmk");
	if (!res)
		printf("删除目录成功\n\n");

	// 获取当前工作目录
	char str[100];
	getcwd(str, sizeof(str) - 1);
	printf("当前工作目录:\n%s\n\n", str);

	// 改变并重新获取当前工作目录
	res = chdir("./testdir/");
	if (!res)
	{
		getcwd(str, sizeof(str) - 1);
		printf("改变后的当前工作目录:\n%s\n\n", str);
	}

	// 移动并重命名目录/文件
	// (注:因为已经改变了当前工作目录,所以现在已经跳转到了testdir目录下,再用"./testdir"就不管用了)
	int res2 = rename("./subdir/", "./newname/");
	int res3 = rename("./a.txt", "./b.txt");
	if (!res2 && !res3)
	{
		printf("重命名成功\n");
		dir_ptr = opendir("./");
		printf("重命名后的打印:\n");
		if (dir_ptr != NULL)
			while ((dir = readdir(dir_ptr)) != NULL)
				printf("%s\n", dir->d_name);
		printf("\n");
	}

	return 0;
}

程序输出的内容如下

下次读取的位置是:0

第一次打印:
.
..
12.txt
subdir
a.txt

第二次打印:
.
..
12.txt
subdir
a.txt

第三次打印:
12.txt
subdir
a.txt

创建目录成功

删除目录成功

当前工作目录:
/home/excious/工程/目录操作

改变后的当前工作目录:
/home/excious/工程/目录操作/testdir

重命名成功
重命名后的打印:
.
..
12.txt
newname
b.txt

四、文件/目录的属性

文件与目录的属性存储与inode节点中,可通过系统调用stat获取文件或者目录的属性,存入struct stat结构中

结构体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,			// 如果文件为字符或块设备时的设备ID
	off_t st_size,			// 如果文件为普通文件时的文件字节数
	time_t st_atime,		// 最近的访问时间
	time_t st_mtime,		// 最近的数据修改时间
	time_t st_ctime,		// 最近的文件状态改变时间
	blksize_t st_blksize,	// 该对象文件系统相关的最佳I/O块大小
	blkcnt_t st_blocks		// 系统为此文件所分配的数据块数
}

1、常用函数

(1).stat

定义在<sys/stat.h>

函数原型如下:

int stat(char *pathname, struct stat *buf);
功能
获取文件的详细信息
注意点
路径中的文件若是符号链接文件,则会获取其所链接到的文件的详细信息
参数
参数说明
pathname要切换到的工作目录路径
buf要存入文件详细信息的结构指针
返回值
成功:返回 0
失败:返回 -1(置 errno

(2).lstat

定义在<sys/stat.h>

函数原型如下:

int lstat(char *pathname, struct stat *buf);
功能
获取文件的详细信息
注意点
路径中的文件若是符号链接文件,则会获取该文件本身的详细信息
参数
参数说明
pathname要切换到的工作目录路径
buf要存入文件详细信息的结构指针
返回值
成功:返回 0
失败:返回 -1(置 errno

(3).fstat

定义在<sys/stat.h>

函数原型如下:

int fstat(int fd, struct stat *buf);
功能
获取已打开文件的详细信息
参数
参数说明
fd已打开文件的文件描述符
buf要存入文件详细信息的结构指针
返回值
成功:返回 0
失败:返回 -1(置 errno

2、常用宏

(1).文件类型宏

定义在<usr/include/bits/stat.h><sys/stat.h>

文件信息中的模式st_mode是一个 16 16 16位的二进制数,由高位(第 15 15 15位)到低位(第 0 0 0位)分别是 4 4 4为文件类型、 3 3 3位特殊权限、 3 3 3位用户权限、 3 3 3位组权限、 3 3 3位其他用户权限

C语言中已经定义好的文件类型宏如下表所示

最高四位二进制数文件类型常量(八进制)文件类型
0100S_IFDIR 0040000目录文件
0010S_IFCHR 0020000字符设备文件
0110S_IFBLK 0060000块设备文件
1000S_IFREG 0100000普通文件
1010S_IFLNK 0120000符号链接文件
1100S_IFSOCK 0140000Socket文件
0001S_IFIFO 0010000命名管道文件

可以把st_mode和相应的mask进行按位与,与上述相应的宏进行比较,判断文件类型

关于掩码mask
要判断文件类型,因为 16 16 16位文件模式中高 4 4 4位是文件类型,因此应取 mask1111000000000000,即 mask = 0170000,该常量

C语言中也同样预定义好了用于判断文件类型的函数宏如下,直接将st_mode传入即可判断:

#define	__S_ISTYPE(mode, mask)	(((mode) & __S_IFMT) == (mask))

#define	S_ISDIR(mode)	 __S_ISTYPE((mode), __S_IFDIR)
#define	S_ISCHR(mode)	 __S_ISTYPE((mode), __S_IFCHR)
#define	S_ISBLK(mode)	 __S_ISTYPE((mode), __S_IFBLK)
#define	S_ISREG(mode)	 __S_ISTYPE((mode), __S_IFREG)

#ifdef __S_IFIFO
# define S_ISFIFO(mode)	 __S_ISTYPE((mode), __S_IFIFO)
#endif

#ifdef __S_IFLNK
# define S_ISLNK(mode)	 __S_ISTYPE((mode), __S_IFLNK)
#endif

(2).文件权限宏

定义在<usr/include/bits/stat.h><sys/stat.h>

文件信息中的模式st_mode 9 9 9位表示文件权限

C语言中已经定义好的文件权限宏如下

#define S_IRUSR 0000400									// 文件所有者读权限
#define S_IWUSR 0000200									// 文件所有者写权限
#define S_IXUSR 0000100									// 文件所有者执行权限
#define S_IRWXU (__S_IREAD | __S_IWRITE | __S_IEXEC)	// 文件所有者读写执行权限

#define S_IRGRP (S_IRUSR >> 3)							// 组用户读权限
#define S_IWGRP (S_IWUSR >> 3)							// 组用户写权限
#define S_IXGRP (S_IXUSR >> 3)							// 组用户执行权限
#define S_IRWXG (S_IRWXI >> 3)							// 组用户读写执行权限

#define S_IROTH (S_IRGRP >> 3)							// 其他用户读权限
#define S_IWOTH (S_IWGRP >> 3)							// 其他用户写权限
#define S_IXOTH (S_IXGRP >> 3)							// 其他用户执行权限
#define S_IRWXO (S_IRWXO >> 3)							// 其他用户读写执行权限

3、使用示例

示例5:尝试使用上述若干系统调用获取文件信息、判断文件类型和文件权限

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	struct stat buf;
	int res = stat("./test.txt", &buf);
	if (res != -1)
	{
		printf("文件的\t【设备ID号】\t\t\t为\t【%ld】\n", buf.st_dev);
		printf("文件的\t【inode号】\t\t\t为\t【%ld】\n", buf.st_ino);
		printf("文件的\t【类型及权限模式】\t\t为\t【%d】\n", buf.st_mode);
		printf("文件的\t【链接数】\t\t\t为\t【%ld】\n", buf.st_nlink);
		printf("文件的\t【所有者用户ID】\t\t为\t【%d】\n", buf.st_uid);
		printf("文件的\t【组ID】\t\t\t为\t【%d】\n", buf.st_gid);
		printf("文件的\t【字节数】\t\t\t为\t【%ld】\n", buf.st_size);
		printf("文件的\t【最近访问时间】\t\t为\t【%ld】\n", buf.st_atime);
		printf("文件的\t【最近数据修改时间】\t\t为\t【%ld】\n", buf.st_mtime);
		printf("文件的\t【最近状态改变时间】\t\t为\t【%ld】\n", buf.st_ctime);
		printf("文件的\t【系统相关最佳I/O块大小】\t为\t【%ld】\n", buf.st_blksize);
		printf("文件的\t【被系统分配到的数据块数】\t为\t【%ld】\n", buf.st_blocks);
	}

	printf("==============================================================\n");

	int fd = open("test.txt", O_RDONLY);
	struct stat buf2;
	int res2 = fstat(fd, &buf2);
	if (res2 != -1)
	{
		printf("文件的\t【设备ID号】\t\t\t为\t【%ld】\n", buf2.st_dev);
		printf("文件的\t【inode号】\t\t\t为\t【%ld】\n", buf2.st_ino);
		printf("文件的\t【类型及权限模式】\t\t为\t【%d】\n", buf2.st_mode);
		printf("文件的\t【链接数】\t\t\t为\t【%ld】\n", buf2.st_nlink);
		printf("文件的\t【所有者用户ID】\t\t为\t【%d】\n", buf2.st_uid);
		printf("文件的\t【组ID】\t\t\t为\t【%d】\n", buf2.st_gid);
		printf("文件的\t【字节数】\t\t\t为\t【%ld】\n", buf2.st_size);
		printf("文件的\t【最近访问时间】\t\t为\t【%ld】\n", buf2.st_atime);
		printf("文件的\t【最近数据修改时间】\t\t为\t【%ld】\n", buf2.st_mtime);
		printf("文件的\t【最近状态改变时间】\t\t为\t【%ld】\n", buf2.st_ctime);
		printf("文件的\t【系统相关最佳I/O块大小】\t为\t【%ld】\n", buf2.st_blksize);
		printf("文件的\t【被系统分配到的数据块数】\t为\t【%ld】\n", buf2.st_blocks);
	}
	close(fd);

	printf("==============================================================\n");

	struct stat buf3;
	int res3 = stat("./test.txt", &buf3);
	if (res3 != -1)
	{
		if ((buf3.st_mode & S_IFMT) == S_IFREG)
			printf("文件test.txt是一个普通文件\n");
		if (S_ISREG(buf3.st_mode))
			printf("文件test.txt是一个普通文件\n");
	}

	printf("==============================================================\n");

	struct stat buf4;
	int res4 = stat("./test.txt", &buf4);
	if (res4 != -1)
	{
		int mode = buf4.st_mode;
		char str_res[] = "----------";
		// 判断文件类型
		if (S_ISDIR(mode)) str_res[0] = 'd';
		if (S_ISCHR(mode)) str_res[0] = 'c';
		if (S_ISBLK(mode)) str_res[0] = 'b';

		// 判断用户权限
		if (mode & S_IRUSR) str_res[1] = 'r';
		if (mode & S_IWUSR) str_res[2] = 'w';
		if (mode & S_IXUSR) str_res[3] = 'x';

		// 判断组权限
		if (mode & S_IRGRP) str_res[4] = 'r';
		if (mode & S_IWGRP) str_res[5] = 'w';
		if (mode & S_IXGRP) str_res[6] = 'x';

		// 判断其他用户权限
		if (mode & S_IROTH) str_res[7] = 'r';
		if (mode & S_IWOTH) str_res[8] = 'w';
		if (mode & S_IXOTH) str_res[9] = 'x';

		// 输出文件类型和权限
		printf("文件类型和权限为%s\n", str_res);
	}

	return 0;
}

程序输出的内容如下

文件的	【设备ID号】				为	【33】
文件的	【inode号】				为	【33875】
文件的	【类型及权限模式】			为	【33188】
文件的	【链接数】				为	【1】
文件的	【所有者用户ID】			为	【1000】
文件的	【组ID】					为	【1000】
文件的	【字节数】				为	【7】
文件的	【最近访问时间】			为	【1618807263】
文件的	【最近数据修改时间】		为	【1618807248】
文件的	【最近状态改变时间】		为	【1618807260】
文件的	【系统相关最佳I/O块大小】	为	【4096】
文件的	【被系统分配到的数据块数】	为	【8】
==============================================================
文件的	【设备ID号】				为	【33】
文件的	【inode号】				为	【33875】
文件的	【类型及权限模式】			为	【33188】
文件的	【链接数】				为	【1】
文件的	【所有者用户ID】			为	【1000】
文件的	【组ID】					为	【1000】
文件的	【字节数】				为	【7】
文件的	【最近访问时间】			为	【1618807263】
文件的	【最近数据修改时间】		为	【1618807248】
文件的	【最近状态改变时间】		为	【1618807260】
文件的	【系统相关最佳I/O块大小】	为	【4096】
文件的	【被系统分配到的数据块数】	为	【8】
==============================================================
文件test.txt是一个普通文件
文件test.txt是一个普通文件
==============================================================
文件类型和权限为-rw-r--r--
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

God-Excious

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值