Linux 系统文件读写相关函数说明

前言:

  在 Linux 系统中必须要使用系统提供的 IO 函数才能基于这些文件描述符完成对相关文件的读写操作。

文件相关函数说明:

open/close:

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

/*
open是一个系统函数, 只能在linux系统中使用, windows不支持
fopen 是标准c库函数, 一般都可以跨平台使用, 可以这样理解:
		- 在linux中 fopen底层封装了Linux的系统API open
		- 在window中, fopen底层封装的是 window 的 api
*/
// 打开一个已经存在的磁盘文件
int open(const char *pathname, int flags);
// 打开磁盘文件, 如果文件不存在, 就会自动创建
int open(const char *pathname, int flags, mode_t mode);

**参数介绍:**

pathname: 被打开的文件的文件名

flags: 使用什么方式打开指定的文件,这个参数对应一些宏值,需要根据实际需求指定

必须要指定的属性 , 以下三个属性不能同时使用,只能任选其一
O_RDONLY: 以只读方式打开文件
O_WRONLY: 以只写方式打开文件
O_RDWR: 以读写方式打开文件
可选属性 , 和上边的属性一起使用
O_APPEND: 新数据追加到文件尾部,不会覆盖文件的原来内容
O_CREAT: 如果文件不存在,创建该文件,如果文件存在什么也不做
O_EXCL: 检测文件是否存在,必须要和 O_CREAT 一起使用,不能单独使用: O_CREAT | O_EXCL
检测到文件不存在,创建新文件
检测到文件已经存在,创建失败,函数直接返回 - 1(如果不添加这个属性,不会返回 - 1)
mode: 在创建新文件的时候才需要指定这个参数的值,用于指定新文件的权限,这是一个八进制的整数

这个参数的最大值为:0777

**返回值:**

成功:返回内核分配的文件描述符,这个值被记录在内核的文件描述符表中,这是一个大于 0 的整数
失败: -1

#include <unistd.h>
int close(int fd);

read/write

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
fd: 文件描述符,open () 函数的返回值,通过这个参数定位打开的磁盘文件
buf: 是一个传出参数,指向一块有效的内存,用于存储从文件中读出的数据
传出参数:类似于返回值,将变量地址传递给函数,函数调用完毕,地址中就有数据了
count: buf 指针指向的内存的大小,指定可以存储的最大字节数
返回值:
大于 0: 从文件中读出的字节数,读文件成功
等于 0: 代表文件读完了,读文件成功
-1: 读文件失败了

write 函数用于将数据写入到文件内部,在通过 open 打开文件的时候需要指定写权限,函数原型如下:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd: 文件描述符,open () 函数的返回值,通过这个参数定位打开的磁盘文件
buf: 指向一块有效的内存地址,里边有要写入到磁盘文件中的数据
count: 要往磁盘文件中写入的字节数,一般情况下就是 buf 字符串的长度,strlen (buf)
返回值:
大于 0: 成功写入到磁盘文件中的字节数
-1: 写文件失败了

上述函数使用

// 文件的拷贝
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    // 1. 打开存在的文件open.txt, 读这个文件,注意文件的路径
    int fd1 = open("./open.txt", O_RDONLY);
    if(fd1 == -1)
    {
        perror("open-readfile");
        return -1;
    }
    // 2. 打开不存在的文件, 将其创建出来, 将从open.txt读出的内容写入这个文件中
    int fd2 = open("bak.txt", O_WRONLY|O_CREAT, 0664);
    if(fd2 == -1)
    {
        perror("open-writefile");
        return -1;
    }
    // 3. 循环读文件, 循环写文件
    char buf[1024];
    int len = -1;
    while( (len = read(fd1, buf, sizeof(buf))) > 0 )
    {
        // 将读到的数据写入到另一个文件中
        write(fd2, buf, len); 
    }
    // 4. 关闭文件
    close(fd1);
    close(fd2);

    return 0;
}

lseek
函数原型如下:

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

off_t lseek(int fd, off_t offset, int whence);
参数:
fd: 文件描述符,open () 函数的返回值,通过这个参数定位打开的磁盘文件
offset: 偏移量,需要和第三个参数配合使用
whence: 通过这个参数指定函数实现什么样的功能
SEEK_SET: 从文件头部开始偏移 offset 个字节
SEEK_CUR: 从当前文件指针的位置向后偏移 offset 个字节
SEEK_END: 从文件尾部向后偏移 offset 个字节
返回值:
成功:文件指针从头部开始计算总的偏移量
失败: -1

**常用文件指针移动函数:**
**文件指针移动到文件头部:**
lseek(fd, 0, SEEK_SET);
**得到当前文件指针的位置:**
lseek(fd, 0, SEEK_CUR); 
**得到文件总大小:**
lseek(fd, 0, SEEK_END);

truncate/ftruncate

函数原型说明如下:

// 拓展文件或截断文件
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
参数:
path: 要拓展 / 截断的文件的文件名
fd: 文件描述符,open () 得到的
length: 文件的最终大小
文件原来 size > length,文件被截断,尾部多余的部分被删除,文件最终长度为 length
文件原来 size < length,文件被拓展,文件最终长度为 length
返回值:成功返回 0; 失败返回值 - 1

stat/lstat 函数

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

int stat(const char *pathname, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);

参数:
pathname: 文件名,要获取这个文件的属性信息
buf: 传出参数,文件的信息被写入到了这块内存中
返回值:函数调用成功返回 0,调用失败返回 -1
这个函数的第二个参数是一个结构体类型,这个结构体相对复杂,通过这个结构体可以存储得到的文件的所有属性信息,结构体原型如下:
struct stat {
    dev_t          st_dev;        	// 文件的设备编号
    ino_t           st_ino;        	// inode节点
    mode_t      st_mode;      		// 文件的类型和存取的权限, 16位整形数  -> 常用
    nlink_t        st_nlink;     	// 连到该文件的硬连接数目,刚建立的文件值为1
    uid_t           st_uid;       	// 用户ID
    gid_t           st_gid;       	// 组ID
    dev_t          st_rdev;      	// (设备类型)若此文件为设备文件,则为其设备编号
    off_t            st_size;      	// 文件字节数(文件大小)   --> 常用
    blksize_t     st_blksize;   	// 块大小(文件系统的I/O 缓冲区大小)
    blkcnt_t      st_blocks;    	// block的块数
    time_t         st_atime;     	// 最后一次访问时间
    time_t         st_mtime;     	// 最后一次修改时间(文件内容)
    time_t         st_ctime;     	// 最后一次改变时间(指属性)
};
获取文件属性实例:
#include <sys/stat.h>

int main()
{
    // 1. 定义结构体, 存储文件信息
    struct stat myst;
    // 2. 获取文件属性 open.txt
    int ret = stat("./open.txt", &myst);
    if(ret == -1)
    {
        perror("stat");
        return -1;
    }
    printf("文件大小: %d\n", (int)myst.st_size);
    return 0;
}

opendir/readdir/closedir 函数

#include <sys/types.h>
#include <dirent.h>
// 打开目录
DIR *opendir(const char *name);
参数: name -> 要打开的目录的名字
返回值: DIR*, 结构体类型指针。打开成功返回目录的实例,打开失败返回 NULL

目录打开之后,就可以通过 readdir () 函数遍历目录中的文件信息了。每调用一次这个函数就可以得到目录中的一个文件信息,当目录中的文件信息被全部遍历完毕会得到一个空对象。先来看一下这个函数原型:
// 读目录
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数:dirp -> opendir () 函数的返回值
返回值:函数调用成功,返回读到的文件的信息,目录文件被读完了或者函数调用失败返回 NULL
函数返回值 struct dirent 结构体原型如下:

struct dirent {
    ino_t          d_ino;       /* 文件对应的inode编号, 定位文件存储在磁盘的那个数据块上 */
    off_t          d_off;       /* 文件在当前目录中的偏移量 */
    unsigned short d_reclen;    /* 文件名字的实际长度 */
    unsigned char  d_type;      /* 文件的类型, linux中有7中文件类型 */
    char           d_name[256]; /* 文件的名字 */
};
关于结构体中的文件类型 d_type,可使用的宏值如下:

DT_BLK:块设备文件
DT_CHR:字符设备文件
DT_DIR:目录文件
DT_FIFO :管道文件
DT_LNK:软连接文件
DT_REG :普通文件
DT_SOCK:本地套接字文件
DT_UNKNOWN:无法识别的文件类型
那么,如何通过 readdir () 函数遍历某一个目录中的文件呢?

// 打开目录
DIR* dir = opendir("/home/test");
struct dirent* ptr = NULL;
// 遍历目录
while( (ptr=readdir(dir)) != NULL)
{
    .......
}

目录操作完毕之后,需要通过 closedir() 关闭通过 opendir() 得到的实例,释放资源。函数原型如下:
// 关闭目录, 参数是 opendir() 的返回值
int closedir(DIR *dirp);
参数:dirp-> opendir () 函数的返回值
返回值:目录关闭成功返回 0, 失败返回 -1

目录函数实例说明–遍历目录:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>

int getMp3Num(const char* path)
{
    // 1. 打开目录
    DIR* dir = opendir(path);
    if(dir == NULL)
    {
        perror("opendir");
        return 0;
    }
    // 2. 遍历当前目录
    struct dirent* ptr = NULL;
    int count = 0;
    while((ptr = readdir(dir)) != NULL)
    {
        // 如果是目录 . .. 跳过不处理
        if(strcmp(ptr->d_name, ".")==0 ||
           strcmp(ptr->d_name, "..") == 0)
        {
            continue;
        }
        // 假设读到的当前文件是目录
        if(ptr->d_type == DT_DIR)
        {
            // 目录
            char newPath[1024];
            sprintf(newPath, "%s/%s", path, ptr->d_name);
            // 读当前目录的子目录
            count += getMp3Num(newPath);
        }
        else if(ptr->d_type == DT_REG)
        {
            // 普通文件
            char* p = strstr(ptr->d_name, ".mp3");
            // 判断文件后缀是不是 .mp3
            if(p != NULL && *(p+4) == '\0')
            {
                count++;
                printf("%s/%s\n", path, ptr->d_name);
            }
        }
    }

    closedir(dir);
    return count;
}

int main(int argc, char* argv[])
{
    // ./a.out path
    if(argc < 2)
    {
        printf("./a.out path\n");
        return 0;
    }

    int num = getMp3Num(argv[1]);
    printf("%s 目录中mp3文件个数: %d\n", argv[1], num);

    return 0;
}

到处就完成了基本函数的说明。

上一篇:文件处理


下一篇:redis——学习之路五(简单的C#使用redis)