系统编程-简洁而不简单的文件操作
来源:公众号【编程珠玑】
作者:守望先生
ID:shouwangxiansheng
我们都听过Linux下一切皆文件,实际上无论是普通的文件读写,还是网络IO读写,它们都有着类似的操作过程。本文通过基本文件IO操作,来了解Linux“一切文件”的读写。当然过程中穿插着很多其他内容。
文件I/O过程
在介绍具体的函数使用之前,我必须说明一下文件I/O的基本过程。它们类似过程如下:
以某种模式打开文件,获取一个文件描述符
对文件进行读写
不需要时,关闭文件描述符
文件描述符是什么?你可以认为是一个对文件进行操作的凭据,你只有通过它才能对文件进行读写。它是一个非负整数。通常0是标准输入,1是标准输出,2是标准错误(参考《如何理解Linux shell中“2>&1”》)。正是有了它们,你的简单程序才可以从控制台读入数据,输出日志,输出错误打印等等。
记得很小的时候,家里连压水的工具都没有,需要用水的时候,都是用一个小点的桶从井里打水。
类比文件I/O操作,打开井盖,拿到绑着绳子的水桶,就像是打开文件,获取文件描述符;而打水的过程,就像对文件进行读写;最后需要的时候,又把桶放回去,并盖上井盖;而这就像关闭文件描述符。
当然了,如果嫌弃里面的小桶打水太慢,有的人可能会用一担大桶用来装水,装满一担后,再挑走使用。而这个过程就像使用了缓冲。(参考《不可不知的三种缓冲》)。
说了这么多废话,文件I/O到底怎么操作呢?本文介绍的是不带缓冲的I/O函数。
打开文件,获取文件描述符
主要函数:
int open(const char *pathname, int flags, mode_t mode);
参数解释:
pathname 文件名
flags 打开选项
这里的文件名应该不用过多解释,但是flags需要做一些说明,
它须指定以下五个中的一个:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
O_EXEC 执行打开
O_SEARCH 搜索打开(针对目录)
而下面的选项是可选的:
O_APPEND 写时追加到文件末尾
O_CREAT 文件不存在时创建,且必须指定文件访问权限位
O_TRUNC 文件存在时,且以只写,或者读写方式打开,则截断长度为0
……
当打开成功时返回文件描述符,否则返回-1,并且设置errno。
读写操作
读写操作主要有两个函数:
ssize_t read(int fd, void *buf,size_t nbytes);
ssize_t write(int fd, const void *buf,size_t nbytes);
参数说明:
fd 文件描述符
buf 要读写的内容
nbytes 读写的内容大小
这里的fd就是前面拿到的文件描述符。篇幅有限, 本文暂不涉及具体的读写介绍。
关闭文件
调用close函数即可,它的参数是前面打开的时候获得的文件描述符
int close(int fd);
成功返回0,失败则返回-1,并且会设置errno。
实例
以上都太过理论化了,那么理论结合实际来看看,究竟是怎样的。
打开一个不存在的文件
这是最简单的情况,现在假设,当前目录下没有test.txt
//来源:公众号【编程珠玑】
int main(void)
{
int fd = open("test.txt",O_WRONLY);
if(-1 == fd)
{
//perror("open failed:");
printf("open failed:%s\n", strerror(errno));
return -1;
}
printf("open ok\n");
char test[] = "wechat:shouwangxiansheng\n";
ssize_t len = write(fd,test,sizeof(test));
if(-1 == len)
{
perror("write failed");
}
close(fd);
}
运行报错:
open failed: No such file or directory
还记得前面说的,如果出错就会设置errno吗?当open返回-1(很多系统接口类似)时,就会设置errno,这个时候就可以调用perror接口打印对应的错误信息。便于我们定位问题。即:
perror("open failed:");
printf("open failed:%s\n", strerror(errno));
上面两种方式都可以打印出错误信息,区别在于,前者输出到标准错误,后者输出到标准输出。
还记得在《不可不知的三种缓冲》中说的吗?标准错误通常是不带缓冲的。
打开一个文件,不存在时创建
既然不存在时,会打开失败,那么不存在就创建好了,这就用到了O_CREATE标志。因此修改open函数那一行:
int fd = open("test.txt",O_WRONLY | O_CREAT);
运行结果:
open ok
并且会在test.txt发现写入的内容。
注意到,多个标志使用|构成flags参数。
打开一个文件,存在时截断
好了,前面已经实现了文件不存在时,创建,存在时也可以正常打开,如果存在时,又不想要原先的内容?那就需要用到O_TRUNC标志。
修改open行如下:
int fd = open("test.txt",O_WRONLY | O_CREAT | O_TRUNC);
现在假设test.txt文件存在,且里面有内容,再次运行后,发现打开文件正常,且内容只有新加入的,而没有之前存在的。
在打开的文件后追加内容
如果想在打开的文件后追加内容,那么可以使用O_APPEND标志:
int fd = open("test.txt",O_WRONLY | O_CREAT | O_APPEND);
这样如果原来test.txt中有内容,则可以往文件中追加内容。
只读打开的文件进行写操作
前面提到了5个打开标志,如果以只读方式尝试写会怎样?
修改open行:
int fd = open("test.txt",O_RDONLY);
你会发现:
open ok
write failed: Bad file descriptor
以只读方式打开,却尝试写,自然是会写失败了。因此对应的操作要设置对应的标志位,否则会失败。
总结
以上就是文件I/O的基本操作。关键就三个步骤:
以某种模式打开
操作
关闭
其中的模式无非是前面提到的一些模式,如读,写,或可读可写,而操作,常见为写入内容,读出内容等等,关闭就更好理解了。以上。
错误处理原则:
返回-1,则出错,会设置errno,可通过perror或者strerror打印错误信息。
相关精彩推荐
关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源