一 文件类型
在Linux系统的设定中,所有可以操作的东西都是文件。所以,Linux将所有的文件分为了七大类别。
类别 | 符号 |
---|---|
普通文件 | - |
目录文件 | d |
链接文件 | l |
管道文件 | p |
字符设备文件 | c |
块设备文件 | b |
网络设备文件 | s |
二 系统IO与标准IO
既然万物皆文件,那么对系统的操作就是对文件的操作。
Linux将对文件的操作方法分成了两种类型,分别是:
① 系统IO:由操作系统直接提供的接口函数
② 标准IO:由标准C库(第三方库)提供的接口函数(通过封装操作系统提供的系统IO,再给用户使用)
它们两者的区别可以翻阅下面的博客:
Linux中的系统IO与标准IO
对于新手而言,应该先系统学习系统IO的编程方法,这会有助于以后理解别人封装好的标准IO。
三 操作类型
正常来说,对文件的操作都离不开下面这四种操作类型:
打开文件,读取文件,修改文件,关闭文件。
在系统IO中,系统帮我们把这四种操作类型都进行了封装,分别以单一的单词作为函数名。
操作类型 | 函数名 |
---|---|
打开文件 | open |
读取文件 | read |
修改/写 文件 | write |
关闭文件 | close |
四 操作文件
下面将通过调用上面四个操作类型函数来完成对文件的操作。
使用这些函数之前,都需要为其添加头文件。但是,在很多时候我们都会忘记需要添加哪些头文件,这时候可以通过终端调用man语句来查阅该函数的相关书籍。
例如,查阅open函数:# man 2 open
,可以看到,使用open函数需要添加: #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>这三个函数。如果没有添加这些函数,编译时将产生警告。
偶尔在书籍2中会查不到该函数的相关内容,可以通过修改man后书籍的数字修改章节。具体章节内容可翻阅该博客:
Linux中man命令的使用方法再解释
1、打开文件
在目录中,新建一个openfile.c文件,一个myfile.txt文件。在openfile.c中编写代码如下:
#include"stdio.h"
/* open 所需头文件 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void){
int open_myfile_fd = open("./myfile.txt",O_RDONLY);
if(open_myfile_fd == -1){
perror("无法打开该文件,因为");
return -1;
}
printf("打开成功\n");
return 0;
}
编译文件并运行可执行文件,发现系统打印了“打开成功”的字样。
此时,将myfile.txt文件删除,再次运行可执行文件,发现系统打印了错误信息“无法打开该文件,因为: No such file or directory”。
这说明:
调用open函数,系统会到open函数中第一个参数,也就是我们指定的位置找到指定的文件名,然后尝试打开,当该文件不存在或无法打开时,open函数会返回一个“-1”值。
可见,open函数是有两个参数的,而第一个参数的含义就是“路径+文件名”。open函数也是带返回值的,而“-1”代表执行错误的意思,当open函数正常执行时,返回值为正数。
其实,在系统IO中,很多函数都是有返回值的,而返回值“-1”都代表着执行错误的意思。这有助于我们及时发现程序的错误,并制止错误程序继续执行。所以在 if( 返回值 == -1 ) 条件为真之后,通常都会写上 return 语句,将程序终止。
那么,第二个参数是什么意思呢?
open函数的第二个参数代表着对即将打开的文件的操作权限。它有三个不同的值可选,分别为:O_RDONLY(只读),O_WRONLY(只写),O_RDWR(读写)。我们可以根据需要修改这个值。
除此之外,open函数还有其他一些参数,对于新手来说可能用不到,如果需要,可以翻阅该博客:linux open()函数各参数说明。
2、读取文件
重新创建一个myfile.txt文件,在其中写下任意内容,如“Hello world”。新建一个readfile.c文件,编写代码如下:
#include"stdio.h"
/* open */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* read 所需头文件 */
#include <unistd.h>
int main(void){
int open_myfile_fd = open("./myfile.txt",O_RDONLY);
int read_myfile_fd ;
char content_myfile[50];
if(open_myfile_fd== -1){
perror("无法打开该文件,因为");
return -1;
}
read_myfile_fd = read(open_myfile_fd,content_myfile,50);
if(read_myfile_fd == -1){
perror("无法读取该文件,因为");
return -1;
}
printf("%s\n",content_myfile);
return 0;
}
编译该文件并执行可执行文件,可以看到终端打印了“Hello world”的字样。
此时,修改代码,将open函数的第二个参数改为 O_WRONLY。编译并执行可执行文件,可以发现终端打印了“无法读取该文件,因为: Bad file descriptor”的字样。
这说明:
① read函数确实能够读取到所打开文件里面的内容;
② read函数也有返回值,无法读取文件时会返回“-1”值;
③ read函数的执行,需要open函数的可读权限。
那怎样理解read函数呢?
read函数有三个参数需要传入。
第一个参数,是指定调用open函数的返回值。上文讲到,如果open函数正常执行,将返回一个正数,其实,该数值就是被打开文件的入口地址。而read函数需要文件的入口地址,来读取文件里面的内容。除此之外,还需要该入口的相关权限。如果该权限为只写,那么将无法读取文件里面的内容。只有该权限为只读,或者可读写时,才可以读取到文件里面的内容。
第二个参数是将读取到的内容,存储到指定的地点。这里声明了一个字符数组,来存放读取到的信息。
第三个参数是读取内容的长度,可以理解为读取多少内容。这里不用担心内容不够多的问题。
最后还有一个隐藏的知识点,这是向老师询问后才知道。那就是read函数有记录功能。假定被读取的文件中有如下内容:ABCDEFG。
此时我们通过调用read函数,读取三个字节的内容,那么将读取到:ABC。
读取完之后,我们再一次调用read函数,再读取三个字节的内容,那么将会读到:DEF。
这就是read函数隐藏的记录功能,它会自动记住偏移量,方便我们向下读取内容。
3、修改文件
删除原有的myfile.txt文件,重新创建一个新的myfile.txt文件,并新建一个writefile.c文件,编写代码如下:
#include"stdio.h"
/* open */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* write 所需头文件 */
#include <unistd.h>
int main(void){
int open_myfile_fd = open("./myfile.txt",O_WRONLY);
int write_myfile_fd ;
char content_myfile[11] = "HELLO WORLD";
if(open_myfile_fd== -1){
perror("无法打开该文件,因为");
return -1;
}
write_myfile_fd = write(open_myfile_fd,content_myfile,11);
if(write_myfile_fd == -1){
perror("无法写入该文件,因为");
return -1;
}
return 0;
}
编译该文件并执行可执行文件。执行可执行文件后,终端并无任何输出。打开myfile.txt文件,发现里面多了一句“HELLO WORLD”的内容,说明此时程序运行正常。
write函数与read函数有很多相似的地方。
① 需要权限。需要修改的文件被open打开的权限需为:O_WRONLY 或者 O_RDWR;
② 需要指定内容的地址。
③ 需要指定写入的数据长度。
第③点有坑,容易出现类似乱码的情况。当给write的第三个参数的数值大于实际所写内容的长度时,最终打开myfile.txt文件会看到内容最后出现类似“\00”的数据。所以,一般最好的做法是以strlen(content_myfile)作为数据长度。
④ write具有记忆功能。
我们修改一下代码,新增写入一段数据。
#include"stdio.h"
/* open */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* write 所需头文件 */
#include <unistd.h>
/* strlen 所需头文件 */
#include <string.h>
int main(void){
int open_myfile_fd = open("./myfile.txt",O_WRONLY);
int write_myfile_fd ;
char content_myfile_1[11] = "Hello world";
char content_myfile_2[11] = "HELLO WORLD";
if(open_myfile_fd== -1){
perror("无法打开该文件,因为");
return -1;
}
write_myfile_fd = write(open_myfile_fd,content_myfile_1,strlen(content_myfile_1));
if(write_myfile_fd == -1){
perror("无法写入该文件,因为");
return -1;
}
write_myfile_fd = write(open_myfile_fd,content_myfile_2,strlen(content_myfile_2));
if(write_myfile_fd == -1){
perror("无法写入该文件,因为");
return -1;
}
return 0;
}
编译该文件并执行可执行文件。再次打开myfile.txt文件,发现里面多了“HELLO WORLD”内容。
这说明,write函数被多次调用后,会将新增内容追加到已写内容后面,而不会覆盖原来旧的内容。
4、关闭文件
关闭文件是最简单的操作,只需要调用close函数,传入被open打开的文件的fd即可:
/* close 所需头文件 */
#include <unistd.h>
// close(int fd);
close(open_myfile_fd);