一、文件:Linux文件操作
1.基于文件指针的文件操作
跳转链接:https://blog.csdn.net/Edward1027/article/details/138673572
2.Linux目录操作
(1)目录路径
0.error
NAME: glibc error reporting functions
SYNOPSIS:
#include <error.h>
#include <errno.h>
void error(int status, int errnum, const char *format, ...)
①第一个参数:为0不退出程序;非0退出程序,相当于exit(1)。
②第二个参数:为0,则不设置errno。若非0,则设置errno。
③第三个参数:相当于perror(" ")
举例:
//Window下的错误处理
perror("getcwd");
exit(1);
//linux下的错误处理
error(1, errno, "getcwd"); //非0退出,不需要换行符,会自动添加\n
error(1, 0, "prefix:"); //不设置errno
1.getcwd
(1)cwd:current working directory
(2)作用:获取当前工作目录的绝对路径
(3)参数
(4)返回值:成功 、失败
(5)特例:getcwd(NULL,0)
查阅man手册:name、synopsis、return value
#include <unistd.h>
char* getcwd(char* buff, size_t size);
(1)参数
①参数1:buff,指向存放当前目录的数组
②参数2:size,数组的大小
(2)返回值
①成功,返回包含当前工作目录的字符串。若buff不为NULL,则返回buff。
②失败,返回NULL,并设置errno
2.chdir
惯用法(Idiom):切换当前工作目录
if(chdir(argv[1]) == -1){
error(1, errno, "chdir %s", argv[1]);
}
3.创建目录:mkdir
惯用法(Idiom):
if(mkdir(argv[1]) == -1){
error(1, errno, "mkdir %s", argv[1]);
}
mode_t mode;
int err = mkdir(argv[1], mode);
if(err){
error(1, errno, "mkdir %s", argv[1]);
}
如何查看
mode_t
究竟是什么类型?
4.删除目录:rmdir
惯用法(Idiom):
if(rmdir(argv[1]) == -1){
error(1, errno, "rmdir %s", argv[1]);
}
5.删除文件:unlink(路径名)
#include <unistd.h>
int unlink(const char *pathname);
(1)参数
pathname: 要删除的文件的路径
(2)返回值
①成功,返回 0。
②失败,返回 -1,并设置 errno 以指示错误
(2)目录流 DIR*
目录项:directory entry -> dirent
0.模型
1.opendir:打开目录流
2.closedir:关闭目录流
惯用法(Idiom):
if(closedir(pdir) == -1) {
error(1, errno, "closedir %s failed.", src);
}
3.readdir:从目录流中读取下一个目录项
目录项(directory entry, dirent) 结构体:inode编号、名字
#include <dirent.h>
#include <sys/types.h>
工作原理:
练习1:使用 readdir 函数读取并打印目录中的所有文件名:
#include <stdio.h>
#include <dirent.h>
int main() {
// 打开目录
DIR* dir = opendir("./test1");
if (dir == NULL) {
error(1, errno, "opendir %s", path);
}
// 读取目录中的每一项
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
printf("%s\n", entry->d_name);
}
// 关闭目录
closedir(dir);
return 0;
}
4.seekdir:可以移动目录流中的位置
5.telldir :返回目录流中现在的位置。
6.rewinddir :重置目录流,即移动到目录流的起始位置。
(3)练习:递归处理目录
①青春版tree命令
//youth_tree.c
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
int directories = 0, files = 0;
void dfs_print(const char* path, int width){
//打开目录流
DIR* stream = opendir(path);
if(stream == NULL){
error(1, errno, "opendir %s failed.\n",path);
}
//遍历每一个目录项
errno = 0;
struct dirent* pdirent;
while((pdirent = readdir(stream)) != NULL){
char* filename = pdirent->d_name;
//忽略.和..
if(strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0){
continue;
}
//打印目录项的名字
for(int i = 0; i < width; i++){
putchar(' ');
}
puts(filename);
if(pdirent->d_type == DT_DIR){
//拼接路径
char subpath[128];
sprintf(subpath, "%s/%s", path, filename);
directories++;
dfs_print(subpath, width + 4);
}else{
files++;
}
} //pdirent == NULL
//关闭目录流
closedir(stream);
if(stream == NULL){
error(1, errno, "closedir failed.");
}
}
int main(int argc, char* argv[]){
//参数校验
if(argc != 2){
error(1, 0, "参数错误。正确用法: ./youth_tree dir");
}
//输出根目录名
puts(argv[1]);
//递归打印目录项
dfs_print(argv[1], 4); //缩进4格
//打印统计信息
printf("\n%d directories, %d files.\n",directories, files);
return 0;
}
②递归复制目录
// copyDir.c
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
// 复制文件
void copyFile(const char* src, const char* dst) {
//1.打开文件
FILE* stream_src = fopen(src, "rb");
if(stream_src == NULL) {
error(1 , errno, "open src file %s failed.",src);
}
FILE* stream_dst = fopen(dst, "wb");
if(!stream_dst){
fclose(stream_src);
error(1, errno, "open dst file %s failed.",dst);
}
//2.确定文件大小
fseek(stream_src, 0, SEEK_END);
long len = ftell(stream_src);
char* content = (char*)malloc((len + 1) * sizeof(char)); // 1 for '\0'
//3.读取文件
rewind(stream_src); //回到文件开头
int bytes;
while(bytes = fread(content, 1, len, stream_src) > 0){
fwrite(content, 1, len, stream_dst);
}
content[bytes] = '\0';
//4.关闭文件
fclose(stream_src);
fclose(stream_dst);
}
void copyDir(const char* src, const char* dst) {
// 创建dst目录
mkdir(dst,0777);
// 打开src目录
DIR* pdir = opendir(src);
if(pdir == NULL){
error(1, errno, "opendir %s failed.",src);
}
// 遍历目录流
errno = 0;
struct dirent* pdirent;
while((pdirent = readdir(pdir)) != NULL) {
// 忽略.和..
char* filename = pdirent->d_name;
if(strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0){
continue;
}
// 如果该目录项是目录,则调用copyDir递归复制
char subsrc[128];
char subdst[128];
sprintf(subsrc, "%s/%s", src, filename);
sprintf(subdst, "%s/%s", dst, filename);
if(pdirent->d_type == DT_DIR){
copyDir(subsrc, subdst);
}
// 如果该目录项是文件,则调用copyFile复制文件
else{
copyFile(subsrc, subdst);
}
}
// 关闭目录流
if(closedir(pdir) == -1){
error(1, errno, "closedir %s failed.", src);
}
}
int main(int argc, char* argv[]) {
//参数校验 ./copyDir src dst
if(argc != 3){
error(1, 0, "参数错误。正确用法:%s src dst",argv[0]);
}
//递归复制目录
copyDir(argv[1], argv[2]);
return 0;
}
③递归删除目录
#include <func.h>
#include <string.h>
void deleteDir(const char* path) {
// 打开目录
DIR* pdir = opendir(path);
if(pdir == NULL){
error(1, errno, "opendir %s", path);
}
errno = 0;
struct dirent* pdirent;
// 遍历目录流,依次删除每一个目录项
while ((pdirent = readdir(pdir)) != NULL) {
// 忽略.和..
if(strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0){
continue;
}
// 如果该目录项是目录,则调用deleteDir递归删除
char subpath[1024];
sprintf(subpath, "%s/%s", path, pdirent->d_name); //拼接路径:上级路径/文件名
if(pdirent->d_type == DT_DIR){
deleteDir(subpath);
}
// 如果该目录项是文件,则调用unlink删除文件
else {
unlink(subpath);
}
} //pdirent == NULL
if(errno){
error(1, errno, "readdir");
}
// 目录为空了,可以删除该目录了
if(rmdir(path) == -1){
error(1, errno, "rmdir %s", path);
}
// 关闭目录流
closedir(pdir);
if(pdir == NULL){
error(1, errno, "closedir failed.");
}
}
int main(int argc, char *argv[])
{
//参数校验
if(argc != 2){
error(1, 0, "参数错误。正确用法: ./rmdir_r dir");
}
//递归删除目录
deleteDir(argv[1]);
return 0;
}
3.基于文件描述符的文件操作
(1)文件描述符
1.文件描述符 (file descriptor,fd) ,非负整数
2.内核用于管理文件的数据结构
①文件描述表:下标是文件描述符fd
②打开文件表:文件位置,即偏移量。有一个指针,指向vnode。
③vnode:是磁盘inode的复制
(2)打开文件、创建文件、关闭文件
①打开文件、创建文件:open()
1.open系统调用的函数原型
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int open(const char *pathname, int flags); //打开文件:文件名,打开方式
int open(const char *pathname, int flags, mode_t mode); //创建文件:文件名,打开方式,权限
(1)参数:[open:两个参数是打开文件,三个参数是创建文件]
①pathname
: 要打开的文件的路径。
②flags
: 标志位 (是32bit的位图)
打开文件的方式和行为。常见的标志包括:
O_RDONLY: 以只读方式打开文件
O_WRONLY: 以只写方式打开文件
O_RDWR: 以读写方式打开文件
O_CREAT: 如果文件不存在,则创建该文件。需要第三个参数 mode 来指定文件的权限。
O_EXCL: 与 O_CREAT 一起使用,确保调用创建文件时文件不存在。
O_TRUNC: 如果文件存在并且是只写或读写模式,打开文件时将其长度截断为 0。
O_APPEND: 以追加模式打开文件。所有写入操作都将追加到文件的末尾。
③mode
: 创建文件时设置的权限(当使用 O_CREAT 标志时需要)。这个参数使用八进制表示文件权限,例如 0644 表示文件所有者有读写权限,组用户和其他用户只有读取权限。
(2)返回值:
①执行成功,返回一个文件描述符,表示已经打开的文件
②执行失败,返回-1,并设置相应的errno
(3)举例:
int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666); //核心
if (fd == -1) {
error(1, errno, "open %s", argv[1]);
}
// 获取了文件描述符
printf("fd = %d\n", fd);
②关闭文件:close()
int close(int fd);//fd表示文件描述词,是先前由open或creat创建文件时的返回值。
将reference count 减1,若引用计数为0,则释放数据结构。
(3)读写文件
使用read和write来读写文件,它们统称为不带缓冲的IO。读写到内核缓冲区。
①读文件:read()
1.函数原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);//文件描述符、缓冲区(起始地址)、长度(最多读取长度)
(1)参数
①文件描述符(file descriptor):这是一个整数,用于标识已经打开的文件。
②缓冲区(buffer):这是一个指向内存位置的指针,数据将被读取到这个缓冲区中。需要定义一个缓冲区buff来接收数据。
③字节数(count):这是要读取的字节数。
(2)返回值
int n = read(fd, recvline, MAXSIZE); //n为成功读取的字节数
①成功,返回读写的字节数目
读到文件末尾EOF,read立刻返回0
②失败,返回-1,并设置errno
②写文件:write()
1.系统调用 write的函数原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count); //count是要写的字节数目
(1)参数
①fd: 文件描述符,表示要写入的文件。可以是一个打开的文件、管道、套接字等。
②buf: 指向要写入数据的缓冲区。
③count: 要写入的字节数。
(2)返回值
①成功时,返回实际写入的字节数(可能小于count)
读到文件末尾EOF,返回0
②失败时,返回-1,并设置errno
#include <func.h>
int main(int argc, char* argv[])
{
// ./copy src dst
if (argc != 3) {
error(1, errno, "Usage: %s src dst", argv[0]);
}
// 1. 打开src和dst
int src = open(argv[1], O_RDONLY);
if (src == -1) {
error(1, errno, "open %s", argv[1]);
}
int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dst == -1) {
error(1, errno, "open %s", argv[2]);
}
// 2. 复制文件
char buf[4096];
int bytes;
while ((bytes = read(src, buf, sizeof(buf))) > 0) {
// 实际读了bytes个字节,所以写入bytes字节
write(dst, buf, bytes);
}
// 3. 关闭文件
close(src);
close(dst);
return 0;
}
补图。内核缓冲区
(4)改变文件大小:ftruncate
1.作用:
改变文件的大小:截断文件,扩大文件大小
2.头文件、参数
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
3.返回值:
执行成功则返回0,失败返回-1
文件空洞,全为’\0’的页,不会分配磁盘空间
(5)内存映射:mmap
1.作用
①mmap:内存映射(memory map),零拷贝复制技术
②将文件的一部分,映射到内存
③使用场景:复制大文件
优势:标准文件操作通常需要将文件内容从磁盘读入内核缓冲区,然后再从内核缓冲区拷贝到用户缓冲区。而使用 mmap 时,文件内容直接映射到用户空间,可以直接访问,避免了多次数据拷贝,从而提高了效率
mmap 是一种内存映射文件的方法,用于将文件内容映射到内存中,从而可以通过内存地址直接访问文件内容。它在处理大文件时非常有用,因为它不需要将整个文件一次性加载到内存中,而是按需加载部分内容。这种方法提高了文件访问的效率,并且可以通过内存操作来读写文件内容。
2.函数原型
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
(1)参数
①addr:映射的起始地址
②length:映射的内存长度
③prot:内存保护标志
1> PROT_READ:页内容可以读取
2> PROT_WRITE:页内容可以写入
3> PROT_EXEC:页内容可以执行
4> PROT_NONE:页内容不能被访问
④flags:指定映射的特性
1> MAP_SHARED:共享映射,对映射区域的修改会写入到文件中,并且对映射了该文件的其他进程可见。
2> MAP_PRIVATE:私有映射,对映射区域的修改不会写入到文件中,并且对其他进程不可见。
3> MAP_ANONYMOUS:匿名映射,不涉及文件,通常与 MAP_PRIVATE 一起使用。
⑤fd:文件描述符,表示要映射的文件。对于匿名映射,应将其设为 -1
⑥offset:文件映射的起始偏移量,必须是页面大小的整数倍
(2)返回值
①成功,返回映射的内存地址
②失败,返回 MAP_FAILED,并设置 errno
off_t 用于表示文件偏移量,实际类型是 long 或 long long
size_t 用于表示对象大小或内存块大小,实际类型是 unsigned int 或 unsigned long
(6)文件定位:lseek
lseek,系统调用,改变文件的位置。一个系统调用,实现三个库函数。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);//fd文件描述词
//whence 可以是下面三个常量的一个
//SEEK_SET 从文件头开始计算, SEEK_CUR 从当前指针开始计算, SEEK_END 从文件尾开始计算
(7)获取文件的元数据信息:fstat
1.stat、fstat函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf); //文件名 stat结构体指针
int fstat(int fd, struct stat *buf); //文件描述词 stat结构体指针
2.举例:
//获取图片的长度
void test1_stat(){
struct stat file_state; //结构体
memset(&st, 0, sizeof(st)); //初始化
stat("./1.png", &file_state); //函数
printf("file size = %ld\n", file_state.st_size);
}
void test2_fstat(){
int fd = open("./1.png",O_RDONLY);
struct stat st; //结构体
memset(&st, 0, sizeof(st)); //初始化
fstat(fd, &st); //函数
printf("file size = %ld\n", st.st_size);
}
3.stat与fstat的区别:
①stat
接受一个文件路径(pathname)作为参数。用于获取指定路径的文件或目录的信息。
②fstat
接受一个文件描述符(fd)作为参数。用于获取已经打开的文件的信息,文件描述符是通过 open、pipe、dup、socket 等系统调用获得的。
4.stat与fstat的共同点:使用struct stat 结构体来存储文件的详细信息
struct stat {
dev_t st_dev; // 文件所在设备的ID
ino_t st_ino; // 文件的节点号
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; // 文件大小(以字节为单位)
blksize_t st_blksize; // 块大小(文件系统I/O操作的基本单位)
blkcnt_t st_blocks; // 分配的块数
time_t st_atime; // 最后访问时间
time_t st_mtime; // 最后修改时间
time_t st_ctime; // 最后状态改变时间
};
(8)文件描述符的复制:dup、dup2
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
1.系统调用:dup
2.dup:返回最小可用的文件描述符
dup2:指定新的文件描述符
3.文件描述符表,fd复制,引用计数+1
4.作用:实现重定向
(9)将修改页写入磁盘:fsync
1.作用
①确保数据持久性:fsync 确保所有写入的数据在调用后已经实际写入到物理存储设备中,避免数据丢失。
②同步文件状态:不仅数据本身被写入磁盘,文件的元数据(如文件大小、修改时间等)也会同步到磁盘上。
fsync 是 Linux 和 UNIX 操作系统中的一个系统调用,用于将文件的所有已修改数据刷新到磁盘上,确保数据持久化。以下是对 fsync 的详细介绍:
适用于需要保证数据安全写入磁盘的场景,如数据库、文件系统等关键应用。
2.函数原型
#include <unistd.h>
#include <fcntl.h>
int fsync(int fd);
(1)参数
fd:文件描述符,是一个打开文件的标识符。
(2)返回值
①成功,返回 0。
②失败,返回 -1,并设置errno
(10)文件描述符 vs 文件流(文件指针)
文件描述符:传输文件
文件流:对文件进行操作
补图11:02
1.fopen是库函数,open是系统调用
1.文件描述符(file descriptor)
int fd = open("file.txt", O_RDONLY);
read(fd, buffer, sizeof(buffer));
write(fd, buffer, sizeof(buffer));
close(fd);
2.文件指针(file pointer)
FILE *fp = fopen("file.txt", "r");
fread(buffer, sizeof(char), sizeof(buffer), fp);
fwrite(buffer, sizeof(char), sizeof(buffer), fp);
fclose(fp);
2.区别:文件描述符更底层,更高效。但没有缓冲机制,导致要进行多次I/O。