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);//如果需要打开的文件不存在creat
    printf("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;
	}
	//删除文件 把文件的硬链接数-1
	unlink("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;
    //对读取到的文件的名字加上:test
    char 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;
        //对读取到的文件的名字加上:test
        char 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;

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

eNSP学习——华为交换机STP配置和选路规则

目录 原理概述 实验内容 实验目的 实验步骤 实验拓扑 实验步骤 基本配置 配置网络中的根交换机 理解根端口的选举 理解指定端口的选举&#xff08;首先比较根路径开销&#xff09; 原理概述 生成树协议&#xff08;英语&#xff1a;Spanning Tree Protocol&#…

excel 选中指定区域

问题 excel 选中指定区域 详细问题 笔者有一个excel数据集&#xff0c;数据量较大&#xff0c;如何快速选中指定区域 解决方案 步骤1、 点击起始单元格 确定单元格坐标&#xff08;建议直接CtrlC复制至剪贴板&#xff09; 具体操作入下图所示 步骤2、 点击结束单元格 …

微信小程序|推箱子小游戏

推箱子游戏是一种经典的益智游戏,通过移动箱子将其推到指定位置,完成关卡的过程。随着小程序的发展,越来越多的人开始在手机上玩推箱子游戏。本文将介绍如何利用小程序实现推箱子游戏,并分享一些技术实现的方法。 目录 引言游戏背景介绍游戏规则及挑战技术实现步骤创建游戏…

Leetcode—1570. 两个稀疏向量的点积【中等】Plus

2024每日刷题&#xff08;一零四&#xff09; Leetcode—1570. 两个稀疏向量的点积 实现代码 class SparseVector { public:SparseVector(vector<int> &nums) {for(int i 0; i < nums.size(); i) {if(nums[i]) {indexNum[i] nums[i];}}}// Return the dotProd…

3 款最好的电脑硬盘数据迁移软件

您将从本页了解 3 款最好的 SSD硬盘数据迁移软件&#xff0c;磁盘供应商提供的软件和可靠的第三方软件。仔细阅读本文并做出您的选择。 什么是数据迁移&#xff1f; 数据迁移是将数据移动到其他计算机或存储设备的过程。在日常工作活动中&#xff0c;常见的数据迁移有三种&…

类Markdown实时绘图编辑器mermaid-live-editor

什么是 Mermaid &#xff1f; Mermaid 是一个基于文本的图表描述语言&#xff0c;它允许你使用简洁的语法来描述各种不同类型的图表和图示&#xff0c;例如流程图、时序图、甘特图等。 什么是 mermaid-live-editor &#xff1f; mermaid-live-editor 是一个基于 Javascript 的在…

springboot3-web开发

跟着尚硅谷学springboot3 0.配置application语法 表示复杂对象person Component ConfigurationProperties(prefix "person") public class Person {private String name;private Integer age;private Date birthday;private Child chlid;private List<Dog>…

实战Vue.js与MySQL:爱心商城项目开发指南

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

14.4.2 Flash读取与修改数据库中的数据

14.4.2 Flash读取与修改数据库中的数据 计数器是网站必不可少的统计工具&#xff0c;使用计数器可以使网站管理者对网站的访问情况有一个清晰的了解。如果仅仅是统计首页访问量的话&#xff0c;用文本文件来存储数据就可以了&#xff0c;但如果统计的数据量比较大的话(如文章系…

The Sandbox 专访|印尼国家足球队主教练申台龙

Q. 请简单介绍一下自己。 我是申台龙&#xff01;我目前担任印度尼西亚国家足球队主教练。我在印尼负责三支国家队的教练工作&#xff0c;分别是 A 组&#xff08;成年队&#xff09;、U-23 和 U-20。在韩国&#xff0c;我的名字是申台龙&#xff08;Shin Tae-yong&#xff09;…

【React】前端项目引入阿里图标

【React】前端项目引入阿里图标 方式11、登录自己的iconfont-阿里巴巴矢量图标库&#xff0c;把需要的图标加入到自己的项目中去&#xff1b;2、加入并进入到项目中去选择Font class 并下载到本地3、得到的文件夹如下4. 把红框中的部分粘贴到自己的项目中&#xff08;public 文…

VirtualBox中Ubuntu硬盘扩容

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

编程语言与编程工具总结

✍️作者简介&#xff1a;小北编程&#xff08;专注于HarmonyOS、Android、Java、Web、TCP/IP等技术方向&#xff09; &#x1f433;博客主页&#xff1a; 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN &#x1f514;如果文章对您些帮助请&#x1f449;关…

iOS 微信分身(Windows手把手教程)

我之前教过大家IOS里面去创建微信应用副本(懂的都懂)。那个教程是MAC的教程版本。就有小伙伴问到&#xff0c;有没有Windows的教程版本呢。其实相差不多&#xff0c;但&#xff0c;不过谁叫我宠粉呢。 如果你使用的Mac版本的请参考这篇文章 1. iOS 微信应用副本 (免费&安…

如何使用wireshark解析二进制文件

目录 目录 1.将已有的packet raw data按照下面格式写入文本文件中 a. Raw IP packet b. Ethernet packet 2.用wiershark导入hex文件 3.设置对应的packet类型 a. Raw IP packet b. Ethernet packet 1.将已有的packet raw data按照下面格式写入文本文件中 a. Raw IP pac…

mysql 一条查询语句执行过程顺序

整体架构 client connectors&#xff1a; mysql提供各种语言连接客户端api&#xff0c;client发送sql语句到server端进行执行 连接器&#xff08;Connectors&#xff09;&#xff1a;连接器负责客户端与服务端进行连接&#xff0c;使用mysql协议或X协议使得客户端可以通过api…

布尔逻辑与逻辑门

计算机为什么使用二进制&#xff1a; 计算机的元器件晶体管只有 2 种状态&#xff0c;通电&#xff08;1&#xff09;& 断电&#xff08;0&#xff09;&#xff0c;用二进制可直接根据元器件的状态来设计计算机。而且&#xff0c;数学中的“布尔代数”分支&#xff0c;可以…

Web前端入门 - HTML JavaScript Vue

ps&#xff1a;刚开始学习web前端开发&#xff0c;有什么不正确、不标准的内容&#xff0c;欢迎大家指出~ Web简介 90年代初期&#xff0c;Web1.0&#xff0c;静态页面&#xff0c;不和服务器交互&#xff0c;网页三剑客指Dreamweaver、Fireworks、Flash2000年代中期&#xf…

《Numpy 简易速速上手小册》第4章:Numpy 数学和统计计算(2024 最新版)

文章目录 4.1 基础统计运算4.1.1 基础知识4.1.2 完整案例&#xff1a;市场调研分析4.1.3 拓展案例 1&#xff1a;股市收益分析4.1.4 拓展案例 2&#xff1a;环境监测数据处理 4.2 线性代数运算4.2.1 基础知识4.2.2 完整案例&#xff1a;解线性方程组4.2.3 拓展案例 1&#xff1…

C Primer Plus第4章编程题

文章目录 printf()和scanf()的*修饰符C Primer Plus第4章编程题 1.printf()和scanf()的*修饰符 printf()和scanf()都可以用*修饰符来修改转换说明的含义。 printf()的*修饰符&#xff1a;如果你不想预先指定字段宽度&#xff0c;希望通过程序来指定&#xff0c;那么可以使用*修…