linux 应用开发笔记---【标准I/O库/文件属性及目录】

一,什么是标准I/O库

标准c库当中用于文件I/O操作相关的一套库函数,实用标准I/O需要包含头文件

二,文件I/O和标准I/O之间的区别

1.标准I/O是库函数,而文件I/O是系统调用

2.标准I/O是对文件I/O的封装

3.标准I/O相对于文件I/O具有更好的可移植性,且效率高

三,FILE文件指针

FILE是一个数据结构体,标准I/O实用FILE指针作为文件句柄

FILE文件指针用于标准I/O库函数,而文件描述符则用于文件I/O系统调用,FILE数据结构定义在标准 I/O 库函数头文件 stdio.h 中

四,标准输入,标准输出和标准错误

**标准输入设备:**计算机系统的标准的输入设备

**标准输出设备:**计算机所连接的键盘

**输出标准设备:**计算机所连接的显示器

五,标准I/O函数

1)打开文件:fopen()

cpp 复制代码
FILE *fopen(const char *path, const char *mode);

path 参数 path 指向文件路径,可以是绝对路径、也可以是相对路径。
mode 参数 mode 指定了对该文件的读写权限

2)关闭文件:fclose()

cpp 复制代码
int fclose(FILE *stream);

stream 为 FILE 类型指针,也就是文件句柄,调用成功返回 0 ;失败将返回 EOF (也就是 -1 )

3)读取/写入文件:fread()/fwrite()

cpp 复制代码
fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

ptr**:**fread()将读取到的数据存放在参数 ptr 指向的缓冲区中
size fread() 从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大
小为 nmemb * size 个字节。
nmemb 参数 nmemb 指定了读取数据项的个数。
stream FILE 指针
返回项:读取或者写入的数据项的数目

写入

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char buf[] = "hello world!";
    FILE *fp = NULL;
    if(NULL == (fp = fopen("./test.txt","w+")))
    {
        perror("open error");
        return 1;
    }
    printf("open ok!!!\r\n");
    if(sizeof(buf)>(fwrite(buf,1, sizeof(buf), fp)))
    {
        printf("fwrite error");
        fclose(fp);
        exit(-1);
    }
    printf("写入成功\r\n");
    fclose(fp);
    return 0;
}

运行结果:

读取

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char buf[20] = "0";
    FILE *fp = NULL;
    int size;
    if(NULL == (fp = fopen("./test.txt","r")))
    {
        perror("open error");
        return 1;
    }
    printf("open ok!!!\r\n");
    if(12>(size = fread(buf,1, 11, fp)))
    {
        if(ferror(fp))
        {
            printf("fread error");
            fclose(fp);
            exit(-1);
        }
        
    }
    printf("成功读取%d 个字节数据: %s\n", size, buf);
    fclose(fp);
    return 0;
}

运行结果:

4)定位函数:fseek()

cpp 复制代码
int fseek(FILE *stream, long offset, int whence);

stream FILE 指针。
offset 与 lseek() 函数的 offset 参数意义相同。
whence 与 lseek() 函数的 whence 参数意义相同

5)判断是否到达文件末尾--feof()函数

cpp 复制代码
int feof(FILE *stream);

6)判断是否发生了错误--ferror()函数

cpp 复制代码
int ferror(FILE *stream);

7)清楚标志--clearerr()函数【自己独立设置标志】

cpp 复制代码
void clearerr(FILE *stream);

8)格式化输入

cpp 复制代码
int printf(const char *format, ...);             
将程序中的字符串信息输出显示到终端


int fprintf(FILE *stream, const char *format, ...); 
将格式化数据写入到由 FILE 指针指定的文件


int dprintf(int fd, const char *format, ...);    
将格式化数据写入到由文件描述符 fd 指定的文件


int sprintf(char *buf, const char *format, ...); 
将格式化数据存储在由参数 buf 所指定的缓冲区中


int snprintf(char *buf, size_t size, const char *format, ...);
使用参数 size 显式的指定缓冲区的大小,如果写入到缓冲区的字节数大于参数 size 指定的大
小,超出的部分将会被丢弃!如果缓冲区空间足够大,snprintf()函数就会返回写入到缓冲区的字符数,与
sprintf()函数相同,也会在字符串末尾自动添加终止字符'\0'

9)格式化输出

cpp 复制代码
int scanf(const char *format, ...);
scanf()函数将用户输入(标准输入)的数据进行格式化转换并进行存储


int fscanf(FILE *stream, const char *format, ...);
从指定文件中读取数据,作为格式转换的输入数据,文件通过 FILE 指针指定


int sscanf(const char *str, const char *format, ...);
从参数 str 所指向的字符串缓冲区中读取数据,作为格式转换的输入数据

六,文件I/O缓冲

1.内核缓冲

read()和write()系统调用是在进行文件读写操作的时候并不会直接访问磁盘设备,而是仅仅在用户空间缓冲区和内核缓冲区之间复制数据。调用write()函数后,会将数据保存到缓存数据区,然后等待内核在某个时刻将缓冲区的数据写入到磁盘设备中,但此时如果read()函数,会直接将数据缓存器的数据返回给应用程序。反之,同理

2.刷新文件I/O的内核缓冲区

对于一些操作,必须强制将文件I/O内核缓冲区中缓存的数据写入到磁盘设备

fsync()函数:

cpp 复制代码
int fsync(int fd);

系统调用 fsync() 将参数 fd 所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回,函数调用成功将返回 0 ,失败返回 -1

fdatasync()函数:

cpp 复制代码
int fdatasync(int fd);

系统调用 fdatasync() 与 fsync() 类似,不同之处在于 fdatasync() 仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据

sync()函数:

cpp 复制代码
void sync(void);

系统调用 sync() 会将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中,该函数没有参数、也无返回值

3.控制文件I/O内核缓冲的标志

1.**O_DSYNC 标志:**write()调用之后调用 fdatasync()函数【元数据不同步】进行数据同步

2.O_SYNC 标志: write()调用都会自动将文件内容数据和元数据刷新到磁盘设备中

4.直接I/O:绕过内核缓冲

在open函数调用添加O_DIRECT 就可以进行调用

直接I/O的对齐限制

⚫ 应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐;
⚫ 写文件时,文件的位置偏移量必须是块大小的整数倍;
⚫ 写入到文件的数据大小必须是块大小的整数倍。

5.stdio缓冲

用户空间 的缓冲区,当应用程序中通过标准 I/O 操作磁盘文件时,为了减少调用系统调用次数,标准 I/O 函数会将用户写入或读取文件的数据缓存在 stdio 缓冲区,然后再一次性 stdio 缓冲区中缓存的数据通过调用系统调用 I/O (文件 I/O )写入到文件 I/O 内核缓冲区或者拷贝到应用程序的 buf 中
三种缓冲类型:
_IONBF
不对 I/O 进行缓冲(无缓冲)。意味着每个标准 I/O 函数将立即调用 write() 或者 read() ,
并且忽略 buf 和 size 参数,可以分别指定两个参数为 NULL 和 0 。标准错误 stderr 默认属于这一种类型,从而保证错误信息能够立即输出
_IOLBF
采用行缓冲 I/O 。在这种情况下,当在输入或输出中遇到换行符 "\n" 时,标准 I/O 才会执
行文件 I/O 操作。对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满),当输 出换行符时,再将这一行数据通过文件 I/O write() 函数刷入到内核缓冲区中;对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出。
_IOFBF
采用全缓冲 I/O 。在这种情况下,在填满 stdio 缓冲区后才进行文件 I/O 操作( read 、 write ),对于输出流,当 fwrite 写入文件的数据填满缓冲区时,才调用 write() 将 stdio 缓冲区中的数据刷入内核缓冲区;对于输入流,每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件默认常用这种缓冲模式

刷新stdio缓冲区
cpp 复制代码
int fflush(FILE *stream);

强制进行文件的刷新,如果参数是NULL,则刷新所有的stdio缓冲区





⚫ 调用 fflush()库函数可强制刷新指定文件的 stdio 缓冲区;
⚫ 调用 fclose()关闭文件时会自动刷新文件的 stdio 缓冲区;
⚫ 程序退出时会自动刷新 stdio 缓冲区(注意区分不同的情况)

I/O缓冲小结:

应用程序调用标准 I/O 库函数将用户数据写入到 stdio 缓冲区中, stdio 缓冲区是
由 stdio 库所维护的用户空间缓冲区。针对不同的缓冲模式,当满足条件时, stdio 库会调用文件 I/O (系统调用 I/O )将 stdio 缓冲区中缓存的数据写入到内核缓冲区中,内核缓冲区位于内核空间。最终由内核向磁盘设备发起读写操作,将内核缓冲区中的数据写入到磁盘(或者从磁盘设备读取数据到内核缓冲区)

七,文件描述符和FILE指针互转

cpp 复制代码
int fileno(FILE *stream);
将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符

成功:文件描述符  失败:NULL

FILE *fdopen(int fd, const char *mode);
将文件描述符转换为FILE指针

成功:文件指针  失败:NULL

八,linux系统的文件类型

文本文件 :内容由文本构成

二进制文件:.o, .bin文件......

符号链接文件:指向另一个文件的路径

管道文件:进程间通信

套接字文件:不同主机的进程间通信
字符设备文件和块设备文件:硬件设备都会对应一个设备文件

获取文件的属性:stat

cpp 复制代码
int stat(const char *pathname, struct stat *buf);

st_dev :该字段用于描述此文件所在的设备。不常用,可以不用理会。
st_ino :文件的 inode 编号。
st_mode:该字段用于描述文件的模式,譬如文件类型、文件权限都记录在该变量中
st_nlink :该字段用于记录文件的硬链接数,也就是为该文件创建了多少个硬链接文件。链接文件可以
分为软链接(符号链接)文件和硬链接文件
st_uid 、 st_gid :此两个字段分别用于描述文件所有者的用户 ID 以及文件所有者的组 ID
st_rdev :该字段记录了设备号,设备号只针对于设备文件,包括字符设备文件和块设备文件,不用理会。
st_size :该字段记录了文件的大小(逻辑大小),以字节为单位。
st_atim 、 st_mtim 、 st_ctim :此三个字段分别用于记录文件最后被访问的时间、文件内容最后被修改的时间以及文件状态最后被改变的时间,都是 struct timespec 类型变量

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    struct stat file_stat;
    int ret;
    ret = stat("./test.txt",&file_stat);
    if(-1 ==  ret)
    {
        perror("open error");
        exit(-1);
    }
    printf("%ld %ld\r\n",file_stat.st_size,file_stat.st_ino);
    exit(0);
    
}

运行结果:

fstat:相对于stat的区别就是,fstat是从fd去获取文件的属性,而stat是从文件路径获取的

lstat()与 stat、fstat 的区别在于,对于符号链接文件,stat、fstat 查阅的是符号链接文件所指向的文件对应的文件属性信息

九,文件属主

文件在创建时,其所有者就是创建该文件的那个用户,Linux 下的每一个文件都有与之相关联的用户 ID 和组 ID,

1.有效用户ID和有效组ID

通常,绝大部分情况下,进程的有效用户等于实际用户(有效用户 ID 等于实际用户 ID),有效组等于实际组(有效组 ID 等于实际组 ID)

2.chown函数:改变文件的所属者和所属组

cpp 复制代码
sudo chown root:root testApp.c
cpp 复制代码
int chown(const char *pathname, uid_t owner, gid_t group);


pathname:用于指定一个需要修改所有者和所属组的文件路径
owner:将文件的所有者修改为该参数指定的用户(以用户 ID 的形式描述);
group:将文件的所属组修改为该参数指定的用户组(以用户组 ID 的形式描述);
返回值:成功返回 0;失败将返回-1,兵并且会设置 errno

⚫ 只有超级用户进程能更改文件的用户 ID ;
⚫ 普通用户进程可以将文件的组 ID 修改为其所从属的任意附属组 ID ,前提条件是该进程的有效用户 ID 等于文件的用户 ID ;而超级用户进程可以将文件的组 ID 修改为任意值
fchown():通过文件的fd去更改文件 lchown(): 通过文件的链接文件本身的属性去更改文件

3.普通权限和特殊权限

普通权限:


特殊权限:

  1. set-user-ID:
    进程对文件进行操作的时候、将进行权限检查,如果文件的 set-user-ID 位权限被设置,内核会将 进程的有效 ID 设置为该文件的用户 ID (文件所有者 ID ),意味着该进程直接获取了文件所有者 的权限、以文件所有者的身份操作该文件
  2. set-group-ID

进程对文件进行操作的时候、将进行权限检查,如果文件的 set-group-ID 位权限被设置,内核会 将进程的有效用户组 ID 设置为该文件的用户组 ID(文件所属组 ID),意味着该进程直接获取了文件所属组成员的权限、以文件所属组成员的身份操作该文件

3.sticky权限

注:
Linux 系统下绝大部分的文件都没有设置 set-user-ID 位权限和 set-group-ID 位权限,所以通常情况下, 进程的有效用户等于实际用户(有效用户 ID 等于实际用户 ID),有效组等于实际组(有效组 ID 等于实际组 ID)
4.目录权限
相对目录里面的文件进行读取,删除,创建......等操作,必须给目录一定的权限,才可以进行对应的操作

4.检查文件的权限

cpp 复制代码
int access(const char *pathname, int mode);    



pathname:文件路径



mode:

⚫ F_OK:检查文件是否存在
⚫ R_OK:检查是否拥有读权限
⚫ W_OK:检查是否拥有写权限
⚫ X_OK:检查是否拥有执行权限
cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int ret;
    ret = access("./test.txt",F_OK);
    if(-1 == ret)
    {
        printf("文件不存在/r/n");
        exit(-1);
    }
    ret = access("./test.txt",R_OK);
    if(!ret)
    {
        printf("可以读取\r\n");
    }
    else
    {
        printf("不可以进行读取\r\n");
    }
    ret = access("./test.txt",W_OK);
    if(!ret)
    {
        printf("可以写入\r\n");
    }
    else
    {
        printf("不可以进行写入\r\n");
    }
    ret = access("./test.txt",X_OK);
    if(!ret)
    {
        printf("不可以进行执行\r\n");
    }
    else
    {
        printf("不可以进行执行\r\n");
    }
    return 0;
}

运行结果:

5.chmod修改文件的权限

cpp 复制代码
int chmod(const char *pathname, mode_t mode);


pathname:
需要进行权限修改的文件路径,若该参数所指为符号链接,实际改变权限的文件是符号链
接所指向的文件,而不是符号链接文件本身。
mode:
该参数用于描述文件权限,与 open 函数的第三个参数一样,这里不再重述,可以直接使用八进
制数据来描述,也可以使用相应的权限宏(单个或通过位或运算符" | "组合)

fchmod():根据fd进行文件权限的修改

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int main(void)
{
    int ret;
    ret = chmod("./test.txt", 0777);
    if(-1 == ret)
    {
        perror("修改失败");
        exit(-1);
    }
    return 0;
}

6.umask函数

文件的实际权限实际上不等于我们设置的权限

mode & ~umask eg. 0777 & (~0002) = 0775

cpp 复制代码
mode_t umask(mode_t mask);


返回值是旧的mask     参数是 新设定的mask      

十,文件的时间属性

修改时间属性: utime(),utimes()

cpp 复制代码
int utime(const char *filename, const struct utimbuf *times);


filename: 文件路径


struct utimbuf {
        time_t actime; /* 访问时间 */
        time_t modtime; /* 内容修改时间 */
};



int utimes(const char *filename, const struct timeval times[2]);


filename: 文件路径

struct timeval {
        long tv_sec; /* 秒 */
        long tv_usec; /* 微秒 */
};



相比之下:utimes的精度更高一些,可以更改到微秒级别
cpp 复制代码
int futimens(int fd, const struct timespec times[2]);


fd:文件描述符。
times:将时间属性修改为该参数所指定的时间值,times 指向拥有 2 个 struct timespec 结构体类型变量
的数组,数组共有两个元素,第一个元素用于指定访问时间,第二个元素用于指定内容修改时间
cpp 复制代码
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#define MY_FILE "./test.txt"
int main(void)
{
    struct timespec tmsp_arr[2];
    int ret;
    int fd;
    /* 检查文件是否存在 */
    ret = access(MY_FILE, F_OK);
    if (-1 == ret) {
            printf("Error: %s file does not exist!\n", MY_FILE);
            exit(-1);
    }
    /* 打开文件 */
    fd = open(MY_FILE, O_RDONLY);
    if (-1 == fd) {
            perror("open error");
            exit(-1);
    }
    /* 修改文件时间戳 */
    #if 0
            ret = futimens(fd, NULL); //同时设置为当前时间
    #endif
    #if 1
            tmsp_arr[0].tv_nsec = UTIME_OMIT;//访问时间保持不变
            tmsp_arr[1].tv_nsec = UTIME_NOW;//内容修改时间设置为当期时间
            ret = futimens(fd, tmsp_arr);
    #endif
}

utimensat()函数:

cpp 复制代码
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);



dirfd:
该参数可以是一个目录的文件描述符,也可以是特殊值 AT_FDCWD;如果 pathname 参数指定
的是文件的绝对路径,则此参数会被忽略。

pathname:
指定文件路径。如果 pathname 参数指定的是一个相对路径、并且 dirfd 参数不等于特殊值
AT_FDCWD,则实际操作的文件路径是相对于文件描述符 dirfd 指向的目录进行解析。如果 pathname 参数
指定的是一个相对路径、并且 dirfd 参数等于特殊值 AT_FDCWD,则实际操作的文件路径是相对于调用进
程的当前工作目录进行解析

times:
与 futimens()的 times 参数含义相同

flags : 
此参数可以为 0 , 也可以设置为 AT_SYMLINK_NOFOLLOW , 如 果 设 置 为
AT_SYMLINK_NOFOLLOW,当 pathname 参数指定的文件是符号链接,则修改的是该符号链接的时间戳,
而不是它所指向的文件

十一,符号链接()软链接和硬链接

硬链接:

ls-li 查看当前的硬链接文件个数,源文件本身也是一个硬链接文件

各个硬链接文件的inode指向的是同一个文件

ln 源文件名称 新创建文件名称 创建硬链接文件

创建硬链接:

cpp 复制代码
int link(const char *oldpath, const char *newpath);

软链接:

ln -s 源文件名称 新创建文件名称 创建硬链接文件

当软链接的源文件删除,其余的文件被称为"悬空链接",原因:软链接文件类似于一种"主从" 关系,软链接内部存着源文件的路径名,当源文件被删除,则无法找到文件路径

创建软链接:

cpp 复制代码
int symlink(const char *target, const char *linkpath);

读取软链接:

cpp 复制代码
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

buf:存放文件缓冲区

bufsiz: 读取的链接文件的大小

创建和删除目录:

cpp 复制代码
int mkdir(const char *pathname, mode_t mode);



int rmdir(const char *pathname);

打开,读取,关闭目录:

cpp 复制代码
DIR *opendir(const char *name);


struct dirent *readdir(DIR *dirp);


int closedir(DIR *dirp);

删除文件:

cpp 复制代码
int unlink(const char *pathname);
cpp 复制代码
int remove(const char *pathname);


pathname 参数指定的是一个非目录文件,那么 remove()去调用 unlink(),如果 pathname 参数指定的是
一个目录,那么 remove()去调用 rmdir()

十二,文件重命名

cpp 复制代码
int rename(const char *oldpath, const char *newpath);
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
        int ret;
        ret = rename("./test", "./test_file");
        if (-1 == ret) {
                perror("rename error");
                exit(-1);
        }
        exit(0);
}
相关推荐
晓数1 小时前
【硬核干货】JetBrains AI Assistant 干货笔记
人工智能·笔记·jetbrains·ai assistant
我的golang之路果然有问题1 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
lwewan1 小时前
26考研——存储系统(3)
c语言·笔记·考研
搞机小能手2 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
nongcunqq2 小时前
爬虫练习 js 逆向
笔记·爬虫
汐汐咯3 小时前
终端运行java出现???
笔记
无敌小茶4 小时前
Linux学习笔记之环境变量
linux·笔记
帅云毅5 小时前
Web3.0的认知补充(去中心化)
笔记·学习·web3·去中心化·区块链
豆豆5 小时前
day32 学习笔记
图像处理·笔记·opencv·学习·计算机视觉
nenchoumi31195 小时前
VLA 论文精读(十六)FP3: A 3D Foundation Policy for Robotic Manipulation
论文阅读·人工智能·笔记·学习·vln