Linux基础IO【文件操作】
前面讲过C语言
的文件操作,文件操作是基础IO
的重要部分,下面我们就来学习一下Linux
中的文件操作
1. 文件认识
先来讲述对于文件的几个疑问
文件是怎么构成的,放在哪里?
- 文件 =
内容
+属性
- 文件被分为两大类:磁盘文件、内存文件(被打开的文件)
- 文件没有被操作的时候,一般放在磁盘上
- 文件被操作的时候,文件属性会被加载到内存中,冯诺依曼体系规定
文件操作的本质是什么?
- 对文件的操作无非就两种:对内容进行操作或者对属性进行操作,这里讲述后者
- 文件操作本质就是将需要的文件属性加载到内存中,操作系统一定同时存在大量的被打开的文件,同时操作系统也要管理这些被打开的文件,通过先描述再组织的方式
- 先描述就是构建在内存中的文件结构体
struct file
,来存储文件属性进行管理,这个结构体可以从磁盘上拿到,再组织就是通过数据结构来组织,比如:链表来连接结构体节点
文件是谁打开的?
- 文件是被操作系统打开的,是由用户创建进程,进程让操作系统完成打开文件的任务
所有语言的文件操作,本质上都是调用系统级接口进行操作,要针对底层系统级文件操作进行学习
2. 回顾C文件接口
先来回顾一下C语言
的文件操作
2.1 打开文件
使用fopen
函数打开文件
FILE * fopen ( const char * filename, const char * mode );
fopen
函数
返回值:打开文件失败返回
NULL
filename参数:要打开的文件名,直接使用文件名,此文件需要位于当前程序目录下,可以使用绝对路径来指定目录存放
mode
参数:文件打开方式w
:只写,文件写入前会先清空文件原内容,如果文件不存在会自动创建它a
:追加,在文件末尾对文件追加写入内容,不会清空原内容r
:只读,打开指定文件进行读取操作,如果文件不存在则会打开失败w+
、a+
、r+
:可读可写、可读可追加、可读可写,其中只有r+
不会自动创建文件
2.2 关闭文件
文件打开使用完后需要关闭,使用fclose
函数关闭文件
int fclose ( FILE * stream );
对FILE*
指针进行操作,只能关闭已打开的文件,文件不存在会报错
FILE* fd = fopen("text.txt", "w"); //打开文件
//...文件使用...
fclose(fd); //关闭文件
2.3 文件写入
C语言
中的文件写入方式
int fputc ( int character, FILE * stream ); //逐字符写入
int fputs ( const char * str, FILE * stream ); //逐行写入
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream ); //二进制格式化写入
int fprintf ( FILE * stream, const char * format, ... ); //格式化写入
int snprintf ( char * s, size_t n, const char * format, ... );
前几个都比较简单,来讲解一下snprintf
s
参数:缓冲区n
参数:缓冲区大小format
参数:格式化输入
使用snprintf
函数将数据写到缓冲区后,可以通过fputs
函数将缓冲区中的数据写入文件中
#include <stdio.h>
#define TEXT "text.txt"
int main()
{
FILE* fd = fopen(TEXT,"w");
if(fd == NULL)
{
perror("fopen fail!");
exit(-1);
}
const char* message = "hello world";
char buffer[256]; //缓冲区
int n = 5;
while(n--)
{
snprintf(buffer, sizeof(buffer), "%s %d\n", message, n); //向缓冲区写入内容
fputs(buffer, fd); //将缓冲区中的内容写入文件
}
fclose(fd);
return 0;
}
2.4 文件读取
C语言
中的文件读取方式
int fgetc ( FILE * stream );
char * fgets ( char * str, int num, FILE * stream );
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
int fscanf ( FILE * stream, const char * format, ... );
int sscanf ( const char * s, const char * format, ...);
这里的sscanf
用于按照一定规则格式化读取字符串
#include <stdio.h>
int main()
{
char s[] = "2023,7,20";
int arr[3];
char* buffer[4];
sscanf(s, "%d,%d,%d", arr, arr + 1, arr + 2);
printf("%d %d %d\n", arr[0], arr[1], arr[2]);
return 0;
}
更多C语言文件操作细节可以查看文章《C语言文件操作详解》
3. 系统文件操作接口
3.1 打开文件open
系统级打开文件接口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);
open
函数讲解
返回值:成功返回新打开的文件描述符,失败返回
-1
pathname
参数:待操作文件名flags
参数:打开文件时,可以传入多个参数选项构成mode
参数:权限设置,文件默认权限为0666
讲解一下flags
参数
flags
是一个int
类型的变量,它有32
个比特位,这里选项有很多,如O_APPEND
、O_CREAT
、O_TRUNC
等32
个比特位每一个都可以表示一个标志位,这样就有32
个标志位,也就是对应的可以有32个选项。这种数据结构就是是位图,使用位图进行多参数传递
知道了特性,下面用一个小demo来展示一下位图
#include <stdio.h>
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
//模拟实现选项传递
void out(int flags)
{
if(flags & ONE) printf("1111\n");
if(flags & TWO) printf("2222\n");
if(flags & THREE) printf("3333\n");
}
int main()
{
out(ONE);
printf("-----------\n");
out(TWO);
printf("-----------\n");
out(ONE | TWO);
printf("-----------\n");
out(ONE | TWO | THREE);
printf("-----------\n");
return 0;
}
每个比特位来设置一个选项,可以直接通过传入参数的形式执行不同的功能
列举几个open
系统调用中的flags
选项
O_APPEND //文件用追加的方式被打开
O_CREAT //如果文件不存在就会自动创建
O_RDONLY //只读模式
O_WRONLY //只写模式
O_TRUNC //清理文件
试用一下
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define TEXT "text.txt"
int main()
{
//两种参数组合体现fopen中的a选项
int fd = open(TEXT, O_CREAT | O_WRONLY, 0666); //权限最好加上
if(fd == -1)
{
perror("open fail1");
return;
}
else
{
printf("fd: %d, errno: %d, errstring: %s\n",fd, errno, strerror(errno));
}
close(fd);
return 0;
}
这里的mode
是权限,有以下几种情况
S_IRWXU 00700 user (file owner) has read, write and execute permission
S_IRUSR 00400 user has read permission
S_IWUSR 00200 user has write permission
S_IXUSR 00100 user has execute permission
S_IRWXG 00070 group has read, write and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others have read, write and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
注意:
- 权限可以直接以数字的形式给出
- 若文件不存在,
mode
参数最好设置,不然的话创建出的文件权限是随机值 umask
默认为0002
,可以自定义
3.2 关闭文件 close
系统级关闭文件接口close
,它是根据文件描述符来关闭文件的
#include <unistd.h>
int close(int fildes);
Linux
下的三个标准流为:stdin
、stdout
、stderr
,分别对应的文件描述符为:0
、1
、2
,可以通过close(1)
来关闭标准流
3.3 文件写入 write
系统级文件写入接口write
#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);
使用方法与fwrite
基本相同,返回值有所差异:成功返回写入的字节总数,失败返回-1
并且errno
被设置
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define TEXT "text.txt"
int main()
{
//三种参数组合体现fopen中的w选择
int fd = open(TEXT, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd == -1)
{
perror("open fail1");
return;
}
else
{
printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
}
const char* message = "hello world\n";
int count = 5;
while(count--)
{
write(fd, message, strlen(message)); //这里不能将'\0'写入文件中
}
close(fd);
return 0;
}
注意:使用write
写入字符串时,不要加上 '\0'
当作结尾,因为对于系统来说,'\0'
也是个普通的字符,'\0'
作为字符串结尾只是 C语言
的规定
3.4 文件读取 read
系统级文件读取接口read
#include <unistd.h>
ssize_t read(int fildes, void *buf, size_t nbyte);
read
函数在读取文件时,也是借助缓冲区进行读取,不过只支持按指定字符数读取,无法按行读取,返回值和write
相同
//读取text.txt中内容
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define TEXT "text.txt"
int main()
{
int fd = open(TEXT, O_RDONLY, 0664);
if(fd == -1)
{
perror("open fail1");
return;
}
char buffer[256]; //缓冲区
size_t ret = read(fd, buffer, sizeof(buffer) - 1);
if(ret > 0)
{
buffer[ret] = '\0';
printf("%s\n", buffer);
}
close(fd);
return 0;
}
//读取myfile.c中的66个字符
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define TEXT "myfile.c"
int main()
{
int fd = open(TEXT, O_RDONLY, 0664);
if(fd == -1)
{
perror("open fail1");
return;
}
int n = 66; //要读取的字符数
char buffer[256];
int pos = 0;
while(n--)
{
read(fd, (char*)buffer + pos, 1);
pos++;
}
printf("%s\n", buffer);
close(fd);
return 0;
}
4. 系统调用和库函数
我们上面回顾了C语言
的文件操作库函数,讲解了文件操作系统级的接口,在Linux
下,无论是什么语言,在进行文件操作时所使用的函数,本质上都是对系统调用接口的封装,在文件操作中,是无法直接与硬件(磁盘)交互的,必须经过系统调用接口到操作系统再到驱动程序
这一途径
Linux基础IO【文件操作】,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!
文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正