目录
一. 文件编程是什么?
Linux文件编程是指在Linux系统中使用编程语言进行文件操作的过程。它涵盖了创建、打开、读取、写入、关闭和删除文件等操作。
在Linux中,一切皆文件,包括普通文件、目录、设备文件等。通过文件编程,可以使用各种编程语言(如C、C++、Python等)利用系统提供的接口和函数对这些文件进行操作。
二. 文件编程的特点
一切皆文件:
在Linux系统中,一切设备、资源和信息都被抽象为文件形式。这意味着开发者可以使用相同的文件接口来处理不同类型的文件,包括普通文件、目录、设备文件等,从而提供了统一的编程方式。
灵活性和自由度高:
Linux文件系统提供了丰富的文件类型和属性设置,以及多种文件操作函数和系统调用。开发者可以根据需求自由创建、管理和操作文件,具有很高的灵活性。
权限和安全控制:
Linux文件系统通过权限控制来保护文件的安全性。每个文件都有相应的权限设置,可以限制文件的读写执行权限,并且可以根据用户和用户组进行访问控制。这使得文件编程在安全性方面具有优势。
多进程/多线程支持:
Linux文件编程充分支持多进程/多线程的并发操作。多个进程/线程可以同时对同一个文件进行读写操作,由操作系统负责处理并发访问的一致性和同步。
跨平台兼容性:
Linux文件系统采用一套标准的接口和命令,使得文件在不同的Linux发行版之间具有较好的兼容性。同时,Linux文件系统也能够与其他操作系统的文件系统进行交互,实现跨平台的文件共享和访问。
三. Linux文件操作原理简述
3.1 文件描述符
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件获创建一个新文件时,内核向文件返回一个文件描述符。当读写一个文件时,用 open 和 creat 函数返回的文件描述符标识该文件,将其作为参数传递给 read 和 write 函数。
UNIX shell使用 文件描述符0 与 进程的标准输入相结合,文件描述符1 与 标准输出相结合,文件描述符2 与 标准错误输出相结合。STDIN_FILENO丶STDOUT_FILENO丶STDERR_FILENO这几个宏代替了0丶1丶2这几个数。文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。
文件描述符的作用域就是当前进程,出了当前进程,文件描述符就没有意义了。
3.2 文件操作原理
① 在linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。
② 对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏。
③ 文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件。
当我们去open打开一个文件时,linux内核做到操作包括:
内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;
内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。
静态文件:存放于磁盘,未被打开的文件
动态文件:当使用open后,在linux内核会产生一个结构体来记录文件的信息,例如fd,buf,信息节点.此时的read,write都是对动态文件进行操作,当close时,才把缓存区所有的数据写回磁盘中。
④ 打开文件以后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而不是针对静态文件的。
当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。
⑤ 为什么这样设计,不直接对块设备操作。
块设备本身读写非常不灵活,是按块读写的, 而内存是按字节单位操作的,而且可以随机操作,很灵活。
四. 文件编程的相关函数
4.1 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);参数 *pathname:
文件路径
flags:
打开文件的三种模式(三选一)
O_RDONLY(可读), O_WRONLY(可写), O_RDWR(可读可写)
另外,选择以上其中一个参数后还可以选以下参数:
O_CREAT
若文件不存在则创建它。使用此选项时,需同时说明参数3mode,其
说明该新文件的存取许可权限;
O_EXCL如果同时指定了O_CREAT,而文件已经存在,则返回-1;
O_APPEND每次写时都加到文件尾端;
O_TRUNC属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或者只写成功打开,则将其长度截短为0。
返回值 成功:文件对应的文件描述符(大于0的整数)
失败:返回 -1(Linux系统中默认 0 - 标准输入,1 - 标准输出,2 - 标准错误)
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { //int open(const char *pathname, int flags); int fd = 0; fd = open("test.txt",O_RDWR); //打开文件失败 if(fd == -1) { printf("fail to open file\r\n"); //若打不开文件,就以读写的方式创建文件,并赋给它可读可写的权限 fd = open("test.txt",O_RDWR|O_CREAT,0600); if(fd == -1) { //创建文件失败 printf("fail to create file\r\n"); } else { //创建文件成功 printf("Successfully create file!\r\n"); } } return 0; }
4.1.1 文件打开创建的补充
① O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { //int open(const char *pathname, int flags); int fd = 0; fd = open("test.txt",O_RDWR|O_CREAT|O_EXCL,0600); //O_CREAT与O_EXCL一起使用,如果文件存在则出错 if(fd == -1) printf("the file is cunzai\r\n"); return 0; }
② O_APPEND 每次写时都加到文件尾端
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { //int open(const char *pathname, int flags); int fd = 0; char *buf = "123456"; //以追加的方式写入 fd = open("test.txt",O_RDWR|O_APPEND); write(fd,buf,strlen(buf)); return 0; }
③ O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或者只写成功打开,则将其长度截短为0
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { //int open(const char *pathname, int flags); int fd = 0; char *buf = "hello"; //将文件中的内容清空再写入 fd = open("test.txt",O_RDWR|O_TRUNC); write(fd,buf,strlen(buf)); return 0; }
4.2 write函数文件写入操作
头文件 #include <unistd.h> 函数原型 ssize_t write(int fd, const void *buf, size_t count); 参数 fd:
文件描述符,即open函数成功的返回值
*buf:
写入到文件的缓冲区
count:
要写入内容的字节数
返回值 成功:返回写入字节大小,没有内容写入则返回0
失败:则返回 -1
示例代码:
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main() { //int open(const char *pathname, int flags); int fd = 0; char *buf = "you are very good!"; fd = open("test.txt",O_RDWR); //打开该文件 if(fd == -1) { //打开文件失败 printf("fail to open file\r\n"); //创建文件,并赋予权限 fd = open("test.txt",O_RDWR|O_CREAT,0600); if(fd == -1) { //创建文件失败 printf("fail to open file\r\n"); } else { //创建文件成功 printf("Successfully opened file\r\n"); } } //ssize_t write(int fd, const void *buf, size_t count); //写操作,将要写的内容放入缓冲区中,然后写到对应的文件中(通过文件描述符) write(fd,buf,strlen(buf)); //即,打开test.txt文件之后,可以看到文件中有我们要存入的内容 close(fd); return 0; }
注意:
每次读写文件之后,要调用close函数来关闭文件,否则会导致文件的破坏内容的丢失。
4.3 read函数文件读操作
头文件 #include <unistd.h> 函数原型 ssize_t read(int fd, void *buf, size_t count); 参数 fd:
文件描述符,即open函数成功的返回值
*buf:
存放读出来内容的缓冲区
count:
文件的字节大小
返回值 成功:返回读取到内容的字节大小,没有内容则返回0
失败:则返回 -1
示例代码:
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { int fd = 0; char *readbuf = NULL; //这是空指针 int read_size = 0; //动态开辟内存 readbuf = (char *)malloc(sizeof(char) * 128 + 1); fd = open("test.txt",O_RDONLY); //以只读的方式,打开该文件 /* * 省略返回值的判断 * */ //ssize_t read(int fd, void *buf, size_t count); //读操作,将文件的内容,读出来并存到缓存区中 read_size = read(fd,readbuf,128); printf("the size is %d,content is %s \r\n",read_size,readbuf); free(readbuf); //释放内存 close(fd); return 0; }
注意:
malloc的时候sizeof那里最好加个1,用来存放\0,因为strlen不会将\0计算进去。
4.4 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
示例代码:
include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { int fd = 0; fd = open("test.txt",O_RDWR); //计算文件的大小用lseek int file_size; file_size = lseek(fd,0,SEEK_END); printf("the file size is %d\r\n",file_size); close(fd); return 0; }
注意:
光标的偏移量(offset)为正数时表示往前偏移,为负数时表示往后偏移。
五. 用C库中的相关函数
5.1 open与fopen的区别
库函数与系统调用:
open()是一个系统调用,而fopen()是一个标准C库函数。open()直接由操作系统提供支持,而fopen()是在C标准库中实现的函数。
文件描述符与文件指针:
open()返回一个整数类型的文件描述符,它是一个非负整数,代表操作系统内核中的一个打开文件。 而fopen()返回一个指向FILE结构体的指针,该结构体包含有关文件的信息。
错误处理方式:
open()在出错时返回-1,并通过errno设置错误代码,需要开发者自己进行错误处理。而fopen()在出错时返回NULL,并可通过errno获取错误代码,同时可以使用perror()或strerror()函数输出错误信息。
文件类型限制:
open()可以用于打开任何类型的文件,包括普通文件、设备文件、管道等。而fopen()主要用于打开普通的文本文件。
I/O操作:
open()提供低级I/O操作函数(如read()和write()),可以直接对文件进行读写。而fopen()则提供了更高级的文件I/O函数(如fread()和fwrite()),通过文件指针进行读写。
使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换;而使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为:如果顺序访问文件,fopen系列的函数要比直接调用open系列的函数快;如果随机访问文件则相反。
5.2 fopen函数打开文件
头文件 #include <stdio.h> 函数原型 FILE *fopen(const char *pathname, const char *mode); 参数 pathname:
文件路径
mode:
"r " 只读 文件必须存在
"w" 只写 文件创建,若存在则清空"a " 只读 打开或创建,在文件末尾追加
带有"+"的 可读可写
带有"b"的 二进制文件返回值 成功:指向该流的文件指针就会被返回
失败:则返回 NULL,并把错误代码存在 error 中。
5.3 fread函数读取操作
头文件 #include <stdio.h> 函数原型 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 参数 ptr:
要往文件写入的内容,是字符串格式
size:
一次写入的字节数
nmemb:写多少次
stream :
文件指针,即fopen函数成功的返回值
返回值 成功:返回一个 size_t 对象,表示元素的总数
失败:如果该数字与 nmemb 参数不同,则会显示一个错误。
5.4 fwrite函数写入操作
头文件 #include <stdio.h> 函数原型 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 参数 ptr:
存放读出来内容的缓冲区
size:
要读取的每个元素的大小,以字节为单位。
nmemb:元素的个数,每个元素的大小为 size 字节。
stream :
文件指针,即fopen函数成功的返回值
返回值 成功:返回成功读取的对象个数
失败:如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。
5.5 fseek函数光标移动
头文件 #include <stdio.h> 函数原型 int fseek(FILE *stream, long offset, int whence); 参数 stream :
文件指针,即fopen函数成功的返回值
offset:
正数表示正向偏移,负数表示负向偏移
whence:
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
返回值 成功:返回0
失败:设置error的值,可以用perror()函数输出错误
5.6 feof函数检测结束符
头文件 #include <stdio.h> 函数原型 int feof(FILE *stream); 参数 stream :
文件指针,即fopen函数成功的返回值
返回值 如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。
六. 小项目
6.1 实现cp指令
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main(int argc,char **argv) { //参数不为3个,则错误 if(argc != 3) { printf("the param is error\r\n"); exit(-1); } //先打开源文件,进行读操作,读到缓冲区 int fdSrc = 0; char *readbuf = NULL; fdSrc = open(argv[1],O_RDWR); /*动态开辟内存,不会造成浪费*/ int size = lseek(fdSrc,0,SEEK_END); readbuf = (char *)malloc(sizeof(char) * size + 8); lseek(fdSrc,0,SEEK_SET); read(fdSrc,readbuf,size); //打开并创建目标文件,并将缓冲区的内容写入该文件 int fdDst = 0; fdDst = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600); write(fdDst,readbuf,size); //关闭操作,将动态文件同步到静态文件中 close(fdSrc); close(fdDst); return 0; }
6.2 修改配置文件
#include "stdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main(int argc,char **argv) { /*修改文件的内容*/ if(argc != 2) { printf("the param is error\r\n"); exit(-1); } //先打开源文件,进行读操作,读到缓冲区 int fdSrc = 0; char *readbuf = NULL; fdSrc = open(argv[1],O_RDWR); /*动态开辟内存,不会造成浪费*/ int size = lseek(fdSrc,0,SEEK_END); readbuf = (char *)malloc(sizeof(char) * size + 8); lseek(fdSrc,0,SEEK_SET); read(fdSrc,readbuf,size); char *p = strstr(readbuf,"LENG="); p = p + strlen("LENG="); *p = '9'; lseek(fdSrc,0,SEEK_SET); write(fdSrc,readbuf,size); close(fdSrc); return 0; }