Linux笔记(4)| 文件IO操作

2020-07-10 10:28:10 浏览数 (1)

最近更忙一些,所以更新频率降低了一些。

今天主要分享的是Linux中的文件IO,所谓IO,也就是输入输出,也就是文件的读和写。主要涉及到文件的打开,读写和关闭。

先说一些编译环境。因为现在讲的是Linux,所以最好是在Linux环境中来编译代码,虽然现在写的这些代码在Windows中也照样能够执行,效果也一样,但是最好还是养成在Linux中编译的习惯,后面更复杂一些的代码可能在两种环境中是不一样的。

要搭建Linux开发环境,通常的做法是安装虚拟机,然后在虚拟机当中安装Linux操作系统,这也是比较普遍的做法。我一开始也是这样做的,但是我的虚拟机有一点问题,即使安装了VMware tools,也无法在Windows与Linux中相互复制粘贴,更为要命的是共享文件夹后来不知道为什么无法使用了,网上的方法都不太行。这两个问题是非常致命的,因为这样子的话就没办法在Windows下写好代码,而必须用vim编辑器写代码,vim编辑器使用体验是很差的,所以必须要有新的解决办法。后来我在Windows下的cmd终端安装了gcc编译器,可以实现代码的编译,只不过生成的代码是.exe类型的,而不是Linux下的.out类型的,当然这个问题也不是很大,最大的问题是Windows下的cmd不能使用man手册,这就导致如果你想查看一个库函数的原型非常麻烦。

于是我今天又发现原来Windows可以在开发者模式下安装Linux子系统,只要去Microsoft store下载一个Ubuntu即可。这个子系统给我的体验还是非常不错的,首先,它解决了复制粘贴的问题,在命令行下,选中即复制,右键即粘贴,非常方便,也可以把Windows中的内容复制到命令行下。另外,它可以进入到电脑中任意的盘里面,解决了虚拟机共享文件夹的问题。下面给出一个截图,具体的安装方法可以自行百度。

可以看到,它和Linux下的命令行操作都是一样的。比虚拟机启动快多了,使用起来还是非常方便的。

好了,上面的内容都是环境的搭建,如果习惯使用虚拟机也没问题,这里只不过提供了另外一种不错的方式。

言归正传,文件操作主要涉及打开、读、写、关闭,还有一些不常用的这里就不介绍了,接下来逐个来分析一下。

1、打开文件

打开文件使用open函数,它的函数原型是

代码语言:javascript复制
int open(const char*pathname, int flags);
int open(const char*pathname, int flags, mode_t mode);

这里再顺便说一下,就是如果我们不熟悉某个库函数的原型是什么样的,可以使用man手册来查询,man 1 xx查linuxshell命令,man 2 xxx查API, man 3 xxx查库函数

这个在前面的文章中都有说过。可以回顾一下:Linux笔记(1)| 常用命令

简单说一下这个函数,它的第一个参数是文件名,第二个参数是以什么样的方式来打开。

Flags可以填入:

O_RDONLY(只读)、 O_WRONLY(只写)、 O_RDWR(可读可写)

另外还有O_APPEND、O_TRUNC。O_APPEND属性就是说,如果你打开一个原本有内容的文件,你再往里面写内容是写在原来内容的后面,而O_TRUNC属性就是把原来内容清空后再写入。如果两个属性一起写,表现的是O_TRUNC属性,也即清空后再写。如果两个都不写,那么新内容会从头开始把原来的内容覆盖,没有覆盖完的就不变。可以看一下这部分代码:

如果使用open打开一个不存在的文件会怎么样呢?答案是会报错。如果想要创建并打开一个文件,就可以再加上O_CREAT,加上这个属性之后,就可以打开一个不存在的文件。如果加上这个属性又去打开一个存在的文件呢?那么里面的内容会被清空。所以这样会造成一个隐患,就是加上了这个属性,但是不小心打开了一个不是你想打开的文件,那么就会造成那个文件丢失。所以为了解决这个问题,通常让O_CREAT和O_EXCL一起使用,这样当你打开一个已经存在的文件的时候,它就会提示你File exists,避免不小心把已经存在的文件清空。

以上就是open的几个属性。另外,在使用了O_CREAT这个属性之后,还可以有第三个参数mode来指定要创建的文件的权限。mode使用4个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用0666。

2、对文件进行写操作

Write函数的原型是

代码语言:javascript复制
ssize_t write(int fd, constvoid *buf, size_t count);

第一个参数是文件描述符,文件描述符可以简单理解是区分文件的标志,比如你打开多个文件可以通过文件描述符来区分。第二个参数是要写入的内容,是一个指针。第三个是要写入的字节数。返回值是实际写入的字节数。如果写入失败会返回-1.这个比较容易。

3、读出文件内容

函数原型:

代码语言:javascript复制
ssize_t read(int fd, void*buf, size_t count);

与write函数相似,这里也不多说

4、关闭文件

代码语言:javascript复制
int close(int fd);

输入参数只有一个文件描述符,返回值如果为负表示关闭失败。

下面写一个简单的程序来概括内容

代码语言:javascript复制
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
char write_buff[]="linux";
char read_buff[50]={0};
void main(void)
{
    //打开文件
    int fd=-1;
    int ret;
    char *pread=NULL;
    fd=open("a.txt",O_RDWR|O_CREAT|O_TRUNC|O_EXCL);
    if(fd<0)
    {
        //printf("打开文件失败n");
        //return;
        perror("打开文件失败");
        exit(-1);
    }
    printf("打开文件成功n");

    //写入内容
    ret=write(fd,write_buff,sizeof(write_buff));
    if(ret<0)
    {
        //printf("写入失败n");
        //return;
        perror("写入失败");
        exit(-1);
    }
    printf("写入成功n");
    printf("实际写入了%d个字节n",ret);

    //读出内容
    lseek(fd,-sizeof(write_buff), SEEK_CUR);
    ret=read(fd,read_buff,sizeof(write_buff));
    if(ret<0)
    {
        //printf("读取失败n");
        //return;
        perror("读取失败");
        exit(-1);
    }
    pread=read_buff;
    printf("读取成功n");
    printf("读取了%d个字节n",ret);
    printf("读取的内容是");
    while(*pread !='')
    {

        printf("%c",*pread);

        pread  ;
    }
    printf("n");
    //关闭文件
    close(fd);

}

补充知识:

errno和perror

(1)errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了。

(2)errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。

(3)errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字(譬如-37),不适应于人看。

(4)linux系统提供了一个函数perror(意思print error),perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后print打印出来。

exit、_exit、_Exit退出进程

(1)当我们程序在前面步骤操作失败导致后面的操作都没有可能进行下去时,应该在前面的错误监测中结束整个程序,不应该继续让程序运行下去了。

(2)我们如何退出程序?

第一种;在main用return,一般原则是程序正常终止return 0,如果程序异常终止则return -1。

第一种:正式终止进程(程序)应该使用exit或者_exit或者_Exit之一。

lseek函数介绍

(1)文件指针:当我们要对一个文件进行读写时,一定需要先打开这个文件,所以我们读写的所有文件都是动态文件。动态文件在内存中的形态就是文件流的形式。

(2)当我们打开一个空文件时,默认情况下文件指针指向文件流的开始。所以这时候去write时写入就是从文件开头开始的。write和read函数本身自带移动文件指针的功能,所以当我write了n个字节后,文件指针会自动向后移动n位。如果需要人为的随意更改文件指针,那就只能通过lseek函数了。如果对一个文件写完之后就去读取它,会发现读出的内容是空的,之所以是空的就是因为文件指针是会在写的时候自动往后移,所以读的时候就是空的。所以读之前应该先使用lseek函数把指针移到文件起始位置,再去读,才能正确读出内容。

以上就是Linux中文件的简单操作。后面将持续更新Linux的其他应用编程。

0 人点赞