Linux 文件IO

目录

linux下的文件分类:

文件描述符原理:(底层原理,可跳过)

虚拟文件系统:

内存中的inode与磁盘中的inode

open函数

函数原型:

形参列表:

代码:

close函数

errno函数

read函数

write函数

lseek函数

文件和目录:

stat函数

fstat函数

文件类型

access函数

符号链接(软连接):

硬链接:

unlink函数

文件的时间

mkdir函数

读目录  


在Linux下的一切接文件(Everything is file in Unix/Linux)。IEEE制定了一套POSIX标准,用于统一Unix系统对文件的接口操作。

linux下的文件分类:

   1、在Linux终端我们可以使用ls -|命令来查看当前目录下的文件信息,在显示文件的属性通常会以如下形式显示:

eg:(终端快捷键:Ctrl+alt+T)

2、在Linux中常见的文件类型有七种:(上图的第一个字母)

文件描述符原理:(底层原理,可跳过)

1、对于Linux内核而言,所有打开的文件都是通过文件描述符引用。

2、文件描述符是一个非负的整数,当使用open函数打开或者使用creat函数创建一个文件的时候,内核向用户进程返回一个文件描述符。当读或者写一个文件的时,该文件的文件描述符将作为参数传递给read或者write函数。

3、一个进程的文件描述符从0开始,并且0/1/2分别代表表标准输入STDIN_FILENO,标准输出STDOUT_FILENO和标准出错STDERR_FILENO。

4、在C语言中面对复杂的事物的时候都会有结构体这个方法,同样每个进程在Linux内核中都有一个task_struct结构体来维护这些进程的相关信息,称之为进程描述符,在操作系统理论中称之为PCB(process control block)。task_struct结构体中有个指针指向struct files struct结构体,称之为文件描述符表,,其中每个表项包含一个指向已经打开的文件指针。

操作理解:

我们首先在Linux终端中进入目录:

命令:cd + 空格 /usr/src/linux-headers-5.11.0-46-generic/include

打开linux/sched.h文件,找到struct task struct结构体,其中有一个成员变量files,是一个struct files_struct*类型的指针变量,描述该进程中被打开的文件.

命令:gedit linux/sched.h

如果显示没有该文件或目录则是么有下载linux内核源码

执行命令:
sudo apt-get install linux-source

ctrl+f查找task_struct  该结构体

因为task_struct结构体中有个指针指向struct files_struct结构体

该files指针就是进程中的文件描述符表

接下来我们进入到Linux/fdtable.h

最下边的数组保存的是进程中打开的所有文件

发现struct files_struct中有一个成员变量fd_array 是个指针数组,数组中的每个元素是struct file*进程中每打开一个文件就用一个struct file结构体来描述,fd array数组中就保存了指向用来描述被打开的文件的struct_file结构体,文件描述符其实就是fd array数组的一个下标!!!

下边我们打开linux/fs.h找到struct file的定义

struct file 结构体中有一个成员变量f_op的数据类型是struct file_operation *,也是在linux/fs.h中被定义的:

这个结构体内定义了一些操作文件的函数指针变量。

struct file 结构体中还有另外一个成员变量f_path,是struct path类型该结构体在linux/path.h中被定义

struct path 结构体中有成员变量dentry,是struct dentry*类型的。

struct dentry 目录项:目录项描述的是文件的逻辑属性,只存在一内存当中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件都是属于目录项,所有的目录项在一起构成了一颗庞大的目录树,。例如: open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyytxt都是一个目录项,VFS在查找的时候,根据层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。
注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。

该结构体在linux/dcache.h中定义,

查看的话同样在上述终端中输入 :gedit linux/dcache.h

struct dentry 结构体是描述这个文件所在的目录项信息,以及inode节点信息(struct inode *d_inode)

在查看函数的时候注意

注意:unsigned long i_ino;//inode号

inode 号是VFS中唯一标识一个文件编号

5、从上述描述可以知道如下结构图

虚拟文件系统:

1、VFS(Virtual Filesystem Switch) 称为虚拟文件系统或虚拟文件系统转换,是一个内核软件层,在具体的文件系统之上抽象的一层,用来处理与Posix文件系统相关的所有调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统,同时也为不同文件系统的通信提供了媒介。

2、我们知道文件系统的种类有很多。除了Linux标准的文件系统Ext2/Ext3/Ext4外,还有很多种文件系统。linux通过叫做VFS的中间层对这些文件系统提供了完美的支持。在大部分情况下,用户通过libc和kernel的VFS交互,不需要关心底层文件系统的具体实现。

3、vfs就是对各种文件系统的一个抽象,它为各种文件系统提供了一个通用的接。

4、VFS具有庞大的体系有兴趣的可以去问一下度娘啦。

内存中的inode与磁盘中的inode

1、我们把内存中的inode结构称为VFS inode,而文件系统以EXT2为代表,把EXT2 inode作为磁盘上的inode代表。

2、内存中的inode结构:VFS inode 包含文件访问权限,属主,组,大小,生成时间,访问时间,最后修改信息等。 它是linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。inode结构中的静态信息取自物理设备上的文件系统,由文件系统指定的函数填写,它只存在于内存中,可以通过inode缓存访问。虽然每个文件都有相应的inode结点,但是只有在需要的时候系统才会在内存中为其建立相应的inode数据结构,建立的inode结构将形成一个链表,我们可以通过遍历这个链表去得到我们需要的文件结点VFS 也为已分配的inode构造缓存和hash table,以提高系统的性能。inode结构中的struct inode_operations *iop为我们提供一个inode操作列表,通过这个列表提供的函数我们可以对VFSinode结点进行各种操作。每个inode结构都有一个i结点号i_ino,在同一文件系统中每一个i结点号都是唯一的。

3、磁盘上的inode: EXT2通过使用inode来定义文件系统的结构以及描述系统中每个文件的管理信息,每个文件都有一个inode且只有一个,即使文件中没有数据,其索引结点也是存在的。每个文件用一个单独的Ext2 inode结构来描述,而且每一个inode都有唯一的标志号。Ext2 inode为内存中的inode结构提供了文件的基本信息,随着内存中inode结构的变化,系统也将更新Ext2 inode中相应的内容。Ext2 inode对应的是Ext2 inode结构。

4、什么是inode?

理解inode,要从文件储存说起,
文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。
操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块”(block) 。这种由多个扇区组成的”块”,是文件存取的最小单位。"块”的大小,最常见的是4KB,即连续八个sector组成一个block。
文件数据都储存在“块“中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点”。
每一个文件都有对应的inode,里面包含了与该文件有关的一些信息
5、磁盘上inode的大小
inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区 (inode table) ,存放inode所包含的信息。
每个inode节点的大小,一般是128字节或256字节,inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%.

6、文件查找

open函数

函数原型:

调用open函数可以打开或者创建一个文件。我们可以在ubuntu的终端中输入命令“man 2 open”查看open的函数帮助手册

命令:man 2 open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

函数解释:

形参列表:

1、形参pathname是要打开或者创建文件的名字,flags参数可以用来说明此函数的多个选项。

2、flag参数:(只列出部分)

  • O_APPEND 以追加的方式打开
  • O_RDWR 以可读可写的方式打开
  • O_RDONLY 以只读的方式打开
  • O_WRONLY 以只写的方式打开
  • O_CREAT 如果文件不存在则创建
  • O_TRUNC 如果文件存在则截短为零

注意:至少要使用 O_RDONLY, O_WRONLY, or O_RDWR 其中一个,如果还有其他的选项例如O_APPEND(追加方式),O_CREAT(如果文件不存在则创建),O_TRUNC(如果文件存在则截短为0)等,则和之前的选项使用或运算,例如:open(file, O_RDWR | O_APPEND)

3、mode参数:

如果flags的选项中有 O_CREAT, 则需要设置mode参数,用来设置被创建的新文件的权限。
该权限可以用八进制的数值表示,例如: open(file, O_RDWR | O_CREAT, 0755)O_APPEND 以追加方式打开。

解释0755

三个框分别代表文件的所有者对文件操作权限,

这个是八进制的表达方式,第一个零代表是8进制,三个一组,如果有权限则给1,0775就代表 111 101 101(文件权限rwx r-x r-x)

代码:

通过文件共享在win在win线创建工程到linux中编译

(具体操作方式参考这篇博客http://t.csdnimg.cn/rQoB6)

进入到共享文件当中之后右键进入到终端

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {int fd;//file discriptor 文件描述符fd = open("1.c",O_RDWR);//如果需要打开的文件不存在则open失败printf("fd: %d\n", fd);if(-1 == fd){perror("open error:");return 0;}return  0;
}

输入命令:gcc main.c -o main

生成了一个新的文件main,继续输入命令

命令:./main

执行main文件

返回-1则说明文件打开失败,没有该文件

在文件中新建一个1.c再次编译运行

修改代码再打开一次文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {int fd;//file discriptor 文件描述符fd = open("1.c",O_RDWR);//如果需要打开的文件不存在则open失败printf("fd: %d\n", fd);if(-1 == fd){perror("open error:");return 0;}fd = open("1.c",O_RDWR);//如果需要打开的文件不存在则open失败printf("fd: %d\n", fd);if(-1 == fd){perror("open error:");return 0;}return  0;
}

编译运行

文件描述符的大小随依次打开的次数增加。

close函数

功能:关闭指定文件 

必填头文件:#include <unistd.h>

函数原型:int close(int fd);

fd:需要关闭的文件的文件描述符

  • 成功返回0
  • 失败返回-1,errno被设置

示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argm,int** argv) {int fd;//file discriptor 文件描述符fd = open("2.c",O_RDWR|O_CREAT,0664);//如果需要打开的文件不存在creatprintf("fd: %d\n", fd);if(-1 == fd){perror("open error:");}fd = open("2.c",O_RDWR);printf("fd: %d\n", fd);if(-1 == fd){perror("open error:");}fd=close(fd);printf("fd: %d\n", fd);return  0;
}

效果图:

注意:关闭文件描述符可以在此被使用

errno函数

1、errno是在“errno.h”中申明的一个进程全局变量,当在程序中调用系统函数失败(返回2(%d))的时候会根据失败的原因自动设置errno的值,成打开文件返回0(%d)。

2、可是使用perror函数打印出错的原因

3、errno被当做一个下标访问出错信息的元素

当文件没打开的时候(效果显示)

printf("errno : %d",errno);

perror(“errMsg:”);

当close负数的是(效果显示)

close(-100);

printf("errno : %d",errno);

perror(“errMsg:”);

疑问:为什么可以打印不同的出错原因???

errno的实质是字符指针数组,其本质是数组,数组内的元素为字符指针

例如:char *errMsgArray[4];

不同的errno返回值代表不同的下标,且分别指向不同的字符串(编译调试的时候可以使用该方式,根据不同的下标返回不同的错误类型)

read函数

功能:从指定文件中读取数据

必填头文件:#include <unistd.h>

函数原型:ssize_t read(int fd, void *buf, size_t count);

  • fd:读取文件的文件描述符
  • buf:读取数据在内存空间中存储的地址
  • count:期待读取数据的最大字数

返回值:

  • 文件实际读取的字节数
  • 成功返回实际读取的字节数,实际读取到的字节数可能比希望读取的count值要小
  • 如果读取到文件的末尾返回0

代码历程:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开文件int fd;//文件描述符fd = open("1.c",O_RDWR);if(-1 == fd){perror("open file error");return 0;}//读取数据char data[100] = {0};char *p_data;p_data = (char *)malloc(100);memset(p_data,0,100);int n;
#if 0//从fd这个文件描述符所代表的文件中最多读取99个字节到p_data这个指针所指向的栈空间中n=read(fd,p_data,99);printf("n:%d\r\n",n);//代表真是读到的字节数printf("p_data:\r\n%s",p_data);//读取到的数据
#endif
#if 1 //多次读取文件的内容,知道读取到文件的末尾
while(n=read(fd,p_data,99))
{printf("n:%d\r\n",n);//代表真是读到的字节数
//    printf("p_data:\r\n%s",p_data);//读取到的数据//堆空间用完之后要先对之前读取的内容清空掉memset(p_data,0,100);
}#endif
//return 0;
}

一次读取文件的效果显示:

多次读取文件直到结束的效果显示:

write函数

功能:向一个文件中写入数据

必填头文件:#include <unistd.h>

函数原型:ssize_t write(int fd, const void *buf, size_t count);

  • fd:读取文件的文件描述符
  • buf:写入的数据在内存空间的存储地址
  • count:期待写入数据的最大字数

返回值:

  • 成功返回写入的字节数
  • 失败返回0

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开文件int fd;//文件描述符
//    fd = open("1.c",O_RDWR);//仅仅是可读可写的方式打开fd = open("1.c",O_RDWR | O_APPEND);//可读可写并且追加的方式打开if(-1 == fd){perror("open file error");return 0;}char* buf = "hello world";//将buf这个指针指向的所有空间中的字节写入到fd指向的文件中
//    write(fd,buf,strlen(buf));//如果不是以追加(O_APPEND)的方式打开文件,那么将会从文件的开始写入数据
//之前写入的数据将会被覆盖掉write(fd,"abc",3);close(fd);return 0;
}

注意:要先确认文件是以什么样的方式打开的,若是不追加则在文件开头从新写入,并且覆盖掉新的内容,若是以追加的方式打开的则直接在原来的内容后边添加新的内容

lseek函数

功能:类似于光标的位置

  1. ,每个打开的文件都有一个与其相关联的“当前文件偏移量”,通常是一个非负整数,用以度量从文件开始处计算字节数。通常文件的读写操作都是从当前文件的偏移量处开始,并使偏移量增加所读写的字节数。
  2. 按系统默认情况,当打开一个文件的时候除非指定文件O_APPEND(追加选项),否则该偏移量被设置为0;
  3. 可是使用lseek显示为一个打开的文件设置偏移量。

必填头文件:

 #include <sys/types.h>
 #include <unistd.h>

函数原型:off_t lseek(int fd, off_t offset, int whence);

  • fd:读取文件的文件描述符
  • offset:偏移量
  • whence:可以是SEEK_SET(文件指针的开始),SEEK_CUR(文件指针当前位置),SEEK_END(文件指针尾)

返回值:

  • 成功返回距离文件开头位置的偏移量
  • 失败返回-1,errno被设置

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开文件int fd;//文件描述符
//    fd = open("1.c",O_RDWR);//仅仅是可读可写的方式打开fd = open("1.c",O_RDWR | O_APPEND);//可读可写并且追加的方式打开if(-1 == fd){perror("open file error");return 0;}int n;n = lseek(fd,0,SEEK_SET);printf("n:%d\r\n",n);//从光标的当前位置设置偏移0个字节,目的想知道当前光标距离文件开头的偏移量n = lseek(fd,0,SEEK_CUR);
//  文件虽然以O_APPEND方式打开,其实光标初始位置还在文件的开头,只是我们写入数据的时候会自动移动到文件的末尾printf("n:%d\r\n",n);//将光标移动到文件的末尾,返回值就就是文件中字符的个数n = lseek(fd,0,SEEK_END);printf("n:%d\r\n",n);return 0;
}

代码效果显示:

文件和目录:

前边几个函数是对文本的基本操作,例如文件的打开,读写,文件的定位等等。下边的函数或者关联词是对文件的属性或者类型以及特殊文件的学习。

stat函数

功能:根据文件名获取文件的信息

必填头文件:

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <unistd.h>

函数原型:int stat(const char *pathname, struct stat *statbuf);

  • pathname:需要查看的文件的名字
  • statbuf:保存文件信息结构体指针

返回值:

  • 成功返回0
  • 失败返回-1,errno被设置

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开文件int fd;//文件描述符struct stat st;//定义一个结构体将文件信息放入这个结构体当中fd = stat("1.c",&st);if(-1 == fd){perror("open file error");return 0;}printf("dev:%ld\r\n",st.st_dev);printf("ino:%lu\r\n",st.st_ino);printf("mode:%hu\r\n",st.st_mode);printf("size:%lu\r\n",st.st_size);return 0;
}

效果显示:

fstat函数

功能:根据文件描述符获取文件的信息

必填头文件:

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <unistd.h>

函数原型: int fstat(int fd, struct stat *statbuf);

  • fd:需要查看的文件的文件描述符
  • statbuf:保存文件信息的结构体指针

返回值:

  • 成功返回0
  • 失败返回-1,errno被设置

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开文件int fd;//文件描述符struct stat st;fd = open("1.c",O_RDWR);if(-1 == fd){perror("open file error");return 0;}int ret;ret = fstat(fd,&st);printf("dev:%ld\r\n",st.st_dev);printf("ino:%lu\r\n",st.st_ino);printf("mode:%hu\r\n",st.st_mode);printf("size:%lu\r\n",st.st_size);return 0;
}

代码效果显示:

注意:fstat函数和stat函数使用起来两者没有什么差别,之前前者获取文件信息的方式是文件描述符,后者是文件名。

文件类型

  1. 在<sys/stat.h>头文件中提供了以下文件类型宏
  2. 以上的宏通过传递struct stat结构体中的st_mode成员判断文件的类型
  3. 如果文件类型判断正确则返回逻辑真,否则则为逻辑假

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开文件int fd;//文件描述符struct stat st;fd = open("1.c",O_RDWR);if(-1 == fd){perror("open file error");return 0;}int ret;ret = fstat(fd,&st);if(S_ISREG(st.st_mode)){printf("Is Regular File\r\n");}return 0;
}

代码效果显示:

access函数

功能:对指定的文件进行权限测试

必填头文件:

   #include <unistd.h>

函数原型:int access(const char *pathname, int mode);

  • pathname:需要查看的文件的名字
  • mode:指定access的作用,取值如下:
  • F_OK值为0,判断文件是否存在
  • X_OK值为1,判断对文件是否可执行权限
  • W_OK值为2,判断对文件是否有写到权限
  • R_OK值为4,判断对文件是否有读的权限
  • 注:后三种可以使用或“|”的方式一起使用,

返回值:

  • 如果测试权限存在返回0
  • 如果测试权限不存在返回-1,errno被设置

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//测试某个文件是否存在int ret;ret = access("1.c",F_OK);printf("ret:%d\r\n",ret);if(access("1_1.txt",F_OK))//如果文件不存在返回-1(非0){
//        创建一个文件creat("1_1.txt",0644);}//    测试文件是否可写if(!access("1_1.txt",W_OK)){printf("write OK!\r\n");}//    测试文件是否可读if(!access("1_1.txt",R_OK)){printf("read OK!\r\n");}//    测试文件是否可执行if(!access("1_1.txt",X_OK)){printf("exec OK!\r\n");}return 0;
}

执行效果图:

符号链接(软连接):

1、符号链接也叫做软连接类似于windows系统的快捷方式,它实际上是一个特殊的文件,其中保存的是另一个文件的位置信息

2、创建符号链接的命令:

ln -s 目标文件 符号链接文件

例如:

ln -s 1.c link.c

效果显示:

,link.c保存的是所链接文件的位置而不是保存的链接文件的内容,这个一点从文件的大小上就可以看出来1.c为12字节,而link.c为3字节。

在其他文件中链接该路径的文件:(只需要将目标文件的路径填写正确即可)

3、符号链接文件中的内容

无法使用cat命令因为使用cat命令打开的文件是1.c所链接的文件里边的内容。

注意:如果我们直接打开软链接文件看到的是链接文件中的内容。如果需要查看软链接中的具体内容我们可以使用readlink命令。

注意:软链接文件与原文件是两个独立的文件,只是在软链接文件中保存了原文件的位置,所以我们可以通软链接文件去访问原文件。

怎么证明???

根据前边的inode可知,不同的文件有不同的inode编号只需要查看1.c和link.c的编号即可。

因为两个文件的inode的编号不一样则说明这是两个不同的文件。

4、如果删除软链接所链接的文件,软链接就会失效。

变红是因为失效了。

但是把软链接文件删掉,并不会将源文件删除。

硬链接:

1、硬链接是指通过索引节点(inode)来进行链接。

2、在Linux系统中,多个文件名指向同一个索引节点(Inode)是正常且允许的。一般这种链接就称之为硬链接。硬链接的作用之一是允许一个文件拥有多个有效路径名,这样用户就可以建立硬链接到重要的文件,防止“误删”源数据。但是硬链接只能在同一个文件系统中的文件进行链接,不能对目录进行创建。

3、硬链接的创建方式

ln 源文件 硬链接文件

例如:

ln 1.c 2.c

4、硬链接文件的验证

硬链接文件和原文件其实是同一个文件,只是文件名称不一样,使用stat命令查看文件的信息的时候inode是相同的。而且硬链接(Links的值为2 ,表示有两个文件名称1.c   2.c)

5、硬链接文件的删除

rm 1.c

删除1.c后2.c还是存在的,只是硬链接数量少了一个 

当硬链接数被减到0的时候,文件这时候也就被“删除了”,没法在文件目录中显示,但是其实文件的内容依然存储在硬盘上。

命令rm在执行删除操作的时候只是删除的硬链接操作,并没有完全删除

使用场景:

  • 对文件进行备份,(使用命令cp可以实现文件的拷贝,但是没法对文件进行同步的更新,而使用硬链接不仅实现了拷贝效果,也是实现了文件的同步更新。)

对硬链接的理解,实际上就是一个文件的别名,不管是1.c还是2.c打开的都是同一个文件

unlink函数

1、说明

执行unlink()函数并不一定会真正的删除文件,他先会检查文件系统中此文件的链接是否为1,如果不是1则说明此文件还有其他链接对象,因此只对此文件的链接数进行减一操作。若链接数为1,并且在此时没有任何进程打开该文件,此内容才会真正的被删除掉。在有进程打开此文件的情况下,则暂时不会删除,直到所有打开该文件的进程全部结束时就会被删除。

2、ulink函数原型

必要头文件:#include <unistd.h>

 int unlink(const char *pathname);

3、形参列表

  • pathname:需要删除的文件的名字

4、返回值:

  • 成功返回0
  • 失败返回-1,errno

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开或者创建一个函数int fd;//文件描述符fd = open("1.txt",O_CREAT | O_RDWR | O_TRUNC,0644 );if(-1 == fd){perror("open error");return 0;}//删除文件 把文件的硬链接数-1unlink("1.txt");//对于临时文件最好是使用创建完之后就使用该函数准备删除文件
//如果此时删除了文件是没办法写入的,也没办法读取write(fd,"yixingyunfei",12);//此时写完数据之后光标还在末尾要想读出数据来得先把光标放到文件的开头lseek(fd,0,SEEK_SET);//读数据char buf[128] = {0};read(fd,buf,128);printf("buf:%s\r\n",buf);while(1);//是程序保持运行状态,return 0;
}

说明:unlink的这种特性经常被程序用来确保即使在程序崩溃时,它所创建的临时文件也不会遗留下来。进程用open或create创建一个文件,然后立刻调用unlink。因为该文件仍旧是打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时,该文件的内容才被删除。

效果显示:

程序执行不结束一直在while循环里边

文件的时间

1、文件时间是Linux重要的属性之一,在Linux操作系统的文件时间属性包含三个时间:最近访问,最近更改,最近改动(状态或者属性的改动)时间。

2、可以使用 stat命令查看一个文件的三个时间。时间分别保存在stuct stat结构体的三个成员变量。

可以通过stat命令来获取三个时间戳具体使用看stat部分。

3、stat结构体中存储的时间类型为time_t,是一个从1970年1月1日0点0分0秒到对应的时间秒数。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {//打开或者创建一个函数int fd;//文件描述符fd = open("1.txt",O_CREAT | O_RDWR | O_TRUNC,0644 );if(-1 == fd){perror("open error");return 0;}struct stat st;fstat(fd,&st);printf("%ld\r\n",st.st_atime);printf("%ld\r\n",st.st_mtime);printf("%ld\r\n",st.st_ctime);return 0;
}

这是一个很大的时间,并不好看。

4、使用localtime函数将time_t类型转化为时间结构体。

必要头文件:#include <time.h>

函数原型:struct tm *localtime(const time_t *timep);

timep:需要转换的数据的地址

tm结构体:

示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>int main() {//打开或者创建一个函数int fd;//文件描述符fd = open("1.txt",O_CREAT | O_RDWR | O_TRUNC,0644 );if(-1 == fd){perror("open error");return 0;}struct stat st;fstat(fd,&st);printf("%ld\r\n",st.st_atime);printf("%ld\r\n",st.st_mtime);printf("%ld\r\n",st.st_ctime);struct tm *t;t=localtime(&st.st_atime);printf("%d-%d-%d  %d:%d:%d\r\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);//获取当前时间的年月日时分秒time_t t1;t1 = time(NULL);t=localtime(&t1);printf("%d-%d-%d  %d:%d:%d\r\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);return 0;
}

效果显示:

mkdir函数

1、必加头文件:

#include <sys/stat.h>
 #include <sys/types.h>

2、函数原型

mkdir函数可以创建一个目录。

int mkdir(const char *pathname, mode_t mode);

3、形参列表

  • pathname:需要创建的目录的名字/路径
  • mode:目录访问权限,可以八进制的方式表示。

4、返回值:

  • 成功返回0
  • 失败返回-1,并且errno被设置

示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>int main() {//创建一个目录
int ret;ret=mkdir("test",0644);if(-1 == ret){perror("mkair error:");return 0;}return 0;
}

代码效果:

蓝色字体即代表一个目录

读目录  

1、打开目录:opendir函数

必要头文件: 

#include <sys/types.h>
#include <dirent.h>

函数原型:DIR *opendir(const char *name);

opendir函数打开name所指定的目录,成功返回一个指向该目录的指针,失败返回NULL,errno被设置

2、读取目录:readdir

每次只会读取一个文件

      必要头文件: #include <dirent.h>

      函数原型:struct dirent *readdir(DIR *dirp);

读取dirp所指向的目录,失败或者读取到了目录的末尾则返回NULL 

示例代码:(当我们在main目录中有一个test目录test目录下有1.c 2.c 3.c)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <dirent.h>int main() {//打开一个目录DIR *dir;dir = opendir("test");if(NULL == dir){perror("opendir error");return 0;}//读取目录下的文件struct dirent *ent;//因为readdir函数每次只能读取一个文件,所以如果我们需要读取目录下的所有文件我们需要循环使用readir函数while(ent = readdir(dir)){printf("%s\r\n",ent->d_name);}return 0;
}

代码效果:

这里咱们得test目录下只有三个文件(1.c 2.c 3.c)但有注意到"."和“..”另两个文件,

这是因为在linux目录下任何文件都有两个文件"."和“..”,我们可以使用ls -la查看,这两个目录是隐藏的。

代码示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <dirent.h>int main() {//打开一个目录DIR *dir;dir = opendir("test");if(NULL == dir){perror("opendir error");return 0;}//读取目录下的文件struct dirent *ent;//因为readdir函数每次只能读取一个文件,所以如果我们需要读取目录下的所有文件我们需要循环使用readir函数while(ent = readdir(dir)){
//        printf("%s\r\n",ent->d_name);
//        使用stat函数获取该文件的属性struct stat st;int ret;ret = stat(ent->d_name,&st);
//    判断文件的类型if(-1 == ret){printf("*****************\r\n");printf("%s is error\r\n",ent->d_name);perror("stat error:");printf("*****************\r\n");continue;}if(S_ISREG(st.st_mode))//判断是否为普通文件{printf("%s is reg_file\r\n",ent->d_name);}else if(S_ISDIR(st.st_mode))//判断是否为目录文件{printf("%s is dir\r\n",ent->d_name);}}return 0;
}

效果显示:

解释:在这里为什么1.c和2.c和3.c为什么找不到呐,但是这三个文件名明明在test目录中呀???

这是因为main可执行文件在readdis的目录中,在使用stat进行查找的时候,查找的目录就是在main可执行文件相同的目录中查找,而不会去test的目录中查找。

如果想要去test目录中读取则需要在文件名字前边加上:test

示例代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <dirent.h>int main() {//打开一个目录DIR *dir;dir = opendir("test");if(NULL == dir){perror("opendir error");return 0;}//读取目录下的文件struct dirent *ent;//因为readdir函数每次只能读取一个文件,所以如果我们需要读取目录下的所有文件我们需要循环使用readir函数while(ent = readdir(dir)){
//        printf("%s\r\n",ent->d_name);
//        使用stat函数获取该文件的属性struct stat st;int ret;//对读取到的文件的名字加上:testchar file_name[128]={0};strcpy(file_name,"test/");strcat(file_name,ent->d_name);//字符拼接ret = stat(file_name,&st);
//    判断文件的类型if(-1 == ret){printf("*****************\r\n");printf("%s is error\r\n",ent->d_name);perror("stat error:");printf("*****************\r\n");continue;}if(S_ISREG(st.st_mode))//判断是否为普通文件{printf("%s is reg_file\r\n", ent->d_name);}else if(S_ISDIR(st.st_mode))//判断是否为目录文件{printf("%s is dir\r\n",ent->d_name);}}return 0;
}

代码效果:

3、递归读取一个目录下的完整内容(目录+文件)

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <dirent.h>
//目录的打开顺序
//1、opendir打开目录
//2、readdir读取目录
//3、stat获取文件信息
//4、判断文件是否是目录是的话继续递归打开目录
//成功返回0,失败返回-1
int read_dis(const char *dirname)
{//打开一个目录DIR *dir;dir = opendir("test");if(NULL == dir){perror("opendir error");return -1;}//读取目录下的文件struct dirent *ent;//因为readdir函数每次只能读取一个文件,所以如果我们需要读取目录下的所有文件我们需要循环使用readir函数while(ent = readdir(dir)){
//        判断该文件是否为“.”'..',要排除“.”'..'对递归的影响
if(strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0)
{continue;
}
//        printf("%s\r\n",ent->d_name);
//        使用stat函数获取该文件的属性struct stat st;int ret;//对读取到的文件的名字加上:testchar file_name[128]={0};strcpy(file_name,dirname);strcat(file_name,"/");strcat(file_name,ent->d_name);//字符拼接ret = stat(file_name,&st);
//    判断文件的类型if(-1 == ret){printf("*****************\r\n");printf("%s is error\r\n",ent->d_name);perror("stat error:");printf("*****************\r\n");continue;}if(S_ISREG(st.st_mode))//判断是否为普通文件{printf("%s is reg_file\r\n", ent->d_name);}else if(S_ISDIR(st.st_mode))//判断是否为目录文件{printf("%s is dir\r\n",ent->d_name);read_dis(file_name);//递归打开这个目录里边的文件}}return 0;
}int main() {read_dis("test");//相对路径要确保可执行文件和test在同一个目录下return 0;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/659161.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue_Router_守卫

路由守卫&#xff1a;路由进行权限控制。 分为&#xff1a;全局守卫&#xff0c;独享守卫&#xff0c;组件内守卫。 全局守卫 //创建并暴露 路由器 const routernew Vrouter({mode:"hash"//"hash路径出现#但是兼容性强&#xff0c;history没有#兼容性差"…

一分钟在SpringBoot项目中使用EMQ

先展示最终的结果: 生产者端: RestController RequiredArgsConstructor public class TestController {private final MqttProducer mqttProducer;GetMapping("/test")public String test() {User build User.builder().age(100).sex(1).address("世界潍坊渤…

中国建设银行,这年终奖噶噶高!!!!(含算法原题)

国企年终 今天刷到一个近期帖子:「中国建设银行&#xff0c;这年终奖噶噶高!!!!」 先撇去具体内容不看&#xff0c;能在自然年的 月初&#xff0c;就把去年的奖金发了的企业&#xff0c;首先值得一个点赞。 再细看内容&#xff0c;年终奖是一个 字头的 位数。 由于国企通常没…

burp靶场--xss下篇【16-30】

burp靶场–xss下篇【16-30】 https://portswigger.net/web-security/all-labs#cross-site-scripting 实验16&#xff1a;允许使用一些 SVG 标记的反射型 XSS ### 实验要求&#xff1a; 该实验室有一个简单的反射型 XSS漏洞。该网站阻止了常见标签&#xff0c;但错过了一些 S…

Excel没有内置统计字数功能,但可以用一些变通的方法

是否需要计算Excel工作簿中某个单元格或单元格范围内的单词数? 出于多种原因,你可能需要计算文本数据中的字数。也许你有逗号分隔的列表,需要计算每个列表中的项目数。 不幸的是,Excel没有内置的单词计数方法。但是有一些聪明的方法可以得到你需要的结果。 这篇文章将向…

三步实现 Sentinel-Nacos 持久化

一、背景 版本&#xff1a;【Sentinel-1.8.6】 模式&#xff1a;【Push 模式】 参照官网介绍&#xff1a;生产环境下使用Sentinel &#xff0c;规则管理及推送模式有以下3种模式&#xff1a; 比较之后&#xff0c;目前微服务都使用了各种各样的配置中心&#xff0c;故采用Pus…

springboot综合案例(一)

文章目录 前言项目开发流程需求分析库表设计编码环节环境搭建mybatis的配置jsp模版引擎的配置日志的配置基本项目工程的配置 功能实现用户注册实现验证码功能实现用户注册 用户登录功能员工列表实现员工信息增删查改员工增加信息员工修改信息删除员工信息 前言 我具体用一个小…

【springboot图书个性化推荐系统】

前言 &#x1f31e;博主介绍&#xff1a;✌全网粉丝15W,CSDN特邀作者、211毕业、高级全栈开发程序员、大厂多年工作经验、码云/掘金/华为云/阿里云/InfoQ/StackOverflow/github等平台优质作者、专注于Java、小程序技术领域和毕业项目实战&#xff0c;以及程序定制化开发、全栈…

WindTerm 安装使用教程

一、WindTerm 功能介绍 WindTerm 是一款 Github 上开源的 SSH 终端工具&#xff0c;它是完全可以比肩 MobaXterm 工具的。其支持的系统及功能如下&#xff1a; 功能支持&#xff1a; SSHTelnetShellTCPSerialSFTPCmdPowerShellGit 二、WindTerm 官网下载 有两种获取方法&…

SpringBoot集成MongoDB(3)|(MongoTemplate的List操作)

SpringBoot集成MongoDB&#xff08;3&#xff09;|&#xff08;MongoTemplate的List操作&#xff09; 文章目录 SpringBoot集成MongoDB&#xff08;3&#xff09;|&#xff08;MongoTemplate的List操作&#xff09;[TOC] 前言一、场景说明一、向数组字段添加元素二、从数组中删…

机器学习 低代码 ML:PyCaret 的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

VirtualBox中Ubuntu硬盘扩容

1.选中要扩容的虚拟机点击属性按钮&#xff0c;选择存储后点击控制器&#xff1a;STAT右边的 按钮 2.创建虚拟硬盘 在弹出框中选择创建按钮&#xff0c;选择VDI后点击下一步按钮 选择动态分配后点击下一步按钮 3.设置文件位置和大小 选择要保存的虚拟硬盘文件路径&#xff0c…

会计试算平衡

目录 一. 试算平衡的意义二. 试算平衡的原理和内容三. 试算平衡表 \quad 一. 试算平衡的意义 \quad ①验证错误 ②便于编制会计报表 试算表根据各分类账借贷余额汇总编制而成&#xff0c;依据试算表编制会计报表将比直接依据分类账来编制更为方便,拥有大量分类账的企业尤为便捷…

basic CNN

文章目录 回顾卷积神经网络卷积卷积核卷积过程卷积后图像尺寸计算公式&#xff1a;代码 padding代码 Stride代码 MaxPooling代码 一个简单的卷积神经网络用卷积神经网络来对MINIST数据集进行分类如何使用GPU代码 练习 回顾 下面这种由线形层构成的网络是全连接网络。 对于图像…

分治 (地毯填补问题)

地毯填补问题 题目描述 相传在一个古老的阿拉伯国家里&#xff0c;有一座宫殿。宫殿里有个四四方方的格子迷宫&#xff0c;国王选择驸马的方法非常特殊&#xff0c;也非常简单&#xff1a;公主就站在其中一个方格子上&#xff0c;只要谁能用地毯将除公主站立的地方外的所有地…

万户 ezOFFICE DocumentEdit_unite.jsp SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

Python下载安装与环境配置

本文将指导您完成Python的下载、安装以及环境配置过程&#xff0c;确保您在编写和运行Python代码时能够获得最佳体验。我们将提供详细的步骤和代码示例&#xff0c;帮助您顺利完成设置。 一、Python下载与安装 访问Python官网&#xff1a;首先&#xff0c;您需要访问Python的官…

Pycharm 关闭/退出烦人的Pytest模式

Pycharm 遇到&#xff1a;Run Python tests in ***.py &#xff0c;但很多时候我们并不需要&#xff0c;真心烦人&#xff01; 如何解决: 1 打开File-Settings &#xff08;图片是新版界面&#xff0c;旧版同样操作&#xff09; 2 Tools 中的Python Integrated Tools 在Tes…

LeetCode —— 137. 只出现一次的数字 II

&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️…

第17次修改了可删除可持久保存的前端html备忘录:增加年月日星期,增加倒计时,更改保存区名称可以多个备忘录保存不一样的信息,匹配背景主题:现代深色

第17次修改了可删除可持久保存的前端html备忘录&#xff1a;增加年月日星期&#xff0c;增加倒计时&#xff0c;更改保存区名称可以多个备忘录保存不一样的信息&#xff0c;匹配背景主题&#xff1a;现代深色 备忘录代码&#xff1a; <!DOCTYPE html> <html lang&quo…