文章目录
- 一、文件:Linux文件操作
- 1.基于文件指针的文件操作
- 2.Linux目录操作
- (1)目录路径
- 0.error
- 1.getcwd
- 2.chdir
- 3.创建目录:mkdir
- 4.删除目录:rmdir
- 5.unlink(路径名)
- (2)目录流 DIR*
- 0.模型
- 1.opendir:打开目录流
- 2.closedir:关闭目录流
- 3.readdir:从目录流中读取下一个目录项
- 4.seekdir:可以移动目录流中的位置
- 5.telldir :返回目录流中现在的位置。
- 6.rewinddir :重置目录流,即移动到目录流的起始位置。
- (3)练习:递归处理目录
- ①青春版tree命令
- ②递归复制目录
- ③递归删除目录
- 3.基于文件描述符的文件操作
- (1)文件描述符
- (2)打开文件、创建文件、关闭文件
- ①打开文件:open()
- ②创建文件:open()、creat()
- ③关闭文件:close()
- (3)读写文件
- ①读文件:read
- ②写文件:write
- (4)改变文件大小:ftruncate
- (5)内存映射:mmap
- (6)文件定位:lseek
- (7)获取文件的元数据信息:fstat
- (8)文件描述符的复制:dup、dup2
- (9)将脏页写入磁盘:fsync
- (10)文件描述符 vs 文件流(文件指针)
一、文件: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
笔记代码
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(路径名)
(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()
int open(const char *pathname, int flags); //文件名 打开方式
int open(const char *pathname, int flags, mode_t mode);//文件名 打开方式 权限
返回值:
①执行成功,返回一个文件描述符,表示已经打开的文件
②执行失败,返回-1,并设置相应的errno
#include <func.h>
int main(int argc, char* argv[])
{
// ./test_open file
if (argc != 2) {
error(1, 0, "Usage: %s file", argv[0]);
}
// int fd = open(argv[1], O_RDWR);
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);
return 0;
}
②创建文件:open()、creat()
int creat(const char *pathname, mode_t mode); //文件名 权限
//creat现在已经不常用了,它等价于
open(pathname, O_WRONLY| O_CREAT | O_TRUNC, mode);
flags选项
③关闭文件: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);//文件描述符、缓冲区(起始地址)、长度(最多读取长度)
需要定义一个缓冲区来接收数据
2.返回值
int n = read(fd, recvline, MAXSIZE); //n为成功读取的字节数
成功返回读写的字节数目,出错返回-1,读到文件末尾EOF,read立刻返回0
②写文件:write
系统调用 write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count); //count是要写的字节数目
对于read和write函数,成功返回读写的字节数目,出错返回-1,读到文件末尾EOF,返回0
#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
将文件的一部分,映射到内存
mmap:内存映射(memory map),零拷贝复制技术
使用场景:复制大文件
优势:标准文件操作通常需要将文件内容从磁盘读入内核缓冲区,然后再从内核缓冲区拷贝到用户缓冲区。而使用 mmap 时,文件内容直接映射到用户空间,可以直接访问,避免了多次数据拷贝,从而提高了效率
mmap 是一种内存映射文件的方法,用于将文件内容映射到内存中,从而可以通过内存地址直接访问文件内容。它在处理大文件时非常有用,因为它不需要将整个文件一次性加载到内存中,而是按需加载部分内容。这种方法提高了文件访问的效率,并且可以通过内存操作来读写文件内容。
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
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结构体指针
(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
(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。