在Linux中,文件是一个相当重要的概念,不光普通的文件需要读写,Linux几乎将所有课读写的东西都抽象成了文件,都采用文件的操作方式进行操作,包括什么设备驱动啦,目录啦,管道啦,符号链接啦,甚至网络间的数据传输,都抽象成了文件,所以,掌握了文件的操作方式,也就几乎能够操作Linux上的一切啦。
文件操作能够管理如此多的东西,所以文件操作也提供了相对比较复杂的函数接口,本章试验几个基本的函数接口,包括open、read、write、lseek和close,他们分别负责打开文件、读文件、写文件、设置文件偏移量以及关闭文件。
下面先给出这几个函数的原型:
#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
返回值:成功,返回文件描述符;若出错,返回-1
#include <unistd.h>
int close(int fd);
返回值:若成功,返回0;若出错,返回-1
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
返回值:若成功,返回新的文件偏移量;若出错,返回-1
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
返回值:若成功,返回已写的字节数;若出错,返回-1
下面对上述函数作出简单说明。
open函数用于将一个用路径字符串表示的文件打开,返回一个文件描述符。文件描述符时一个非负整数,通常调用该函数时,返回的文件描述符都是当前环境下可用的最小整数值。一般一个shell环境下都已经预先打开了3个描述符:0表示标准输入,1表示标准输出,2表示标准出错。open的第一个参数就是要打开的文件。第二个参数oflag用来说明此函数的选项,该选项有多个可选值,本章试验只介绍几个比较常用的:
O_RDONLY 只读打开。
O_WRONLY 只写打开。
O_RDWR 读写打开。
O_EXEC 只执行打开。
O_SEARCH 只搜索打开(应用于目录)。
以上五个标识必须且只能指定一个。一般来说常用的是前3个。
O_APPEND 每次写时都追加到文件的尾端。
O_CREAT 若此文件不存在,则创建它。使用此选项时,open函数需要同时说明第三个参数mode,用mode指定该新文件的访问权限。
O_TRUNC 如果此文件存在,而且为只写或读写成功打开,则将其长度截断为0。
还有其他的标志,在后面试验到更高级的文件操作时,将顺便提及,这里不做过多介绍了。当同时指定多个oflag参数时,要用或标识连接起来。
openat函数提供了一种相对路径打开文件的方法,有一下3种可能:
1.path参数指定的是绝对路径名,在这种情况下,fd参数被忽略,openat函数等同于open
2.path参数指定的是相对路径名,fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取的。
3.path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD。在这种情况下,路径名在当前工作目录中获取。
Linux还提供了好多以at结尾的系统调用,其参数均符合上述3种可能性,后面就不再赘述了。
close函数用于关闭一个打开的文件,在文件不使用时,要关闭文件,释放文件描述符,同时,也会释放该进程加在该文件上的所有记录锁。
lseek函数用于设置文件的偏移量,表示将文件的偏移量设置成从whence算起offset个字节的偏移量。whence取值如下:
- 若whence是SEEK_SET,则从文件开始处算起
- 若whence是SEEK_CUR,则从文件的当前偏移量算起
- 若whence是SEEK_END,则从文件的结尾算起
read函数从文件中读数据,存放到buf中,nbytes指出每次想要读取的长度。可能文件中没有那么多数据可读了,可以通过返回值确定实际读到的字节数。如果已经达到文件尾了,则会返回0。
write函数将buf中的数据写入文件,写入字节数为nbytes。
下面做第一个实验,已只写并且O_TRUNC的模式打开文件/tmp/a.txt,然后向其中写入字符串"Hello World!",然后关闭文件,再以只读的方式打开文件,读取文件内容输出到标准输出,然后用lseek将文件偏移量放到文件的第六个字节处,再次读取文件内容并输出到标准输出。代码如下:
void fileTest1(){
int fd = open("/tmp/a.txt", O_WRONLY | O_TRUNC);
if(-1 == fd) {
perror("open error");
return;
}
if (-1 == write(fd, "Hello World!", sizeof("Hello World!"))) {
perror("write error");
return;
}
if (-1 == close(fd)) {
perror("close error");
return;
}
fd = open("/tmp/a.txt", O_RDONLY);
if (-1 == fd) {
perror("open error");
return;
}
char tmp[20];
ssize_t readsize = read(fd, tmp, 20);
if (-1 == readsize) {
perror("read error");
return;
}
printf("%s\n", tmp);
if (-1 == lseek(fd, 6, SEEK_SET)) {
perror("lseek error");
return;
}
readsize = read(fd, tmp, 20);
if (-1 == readsize) {
perror("read error");
return;
}
printf("%s\n", tmp);
}
运行结果为:
Hello World!
World!
这里看到,我们在第一次打开文件时指定了O_TRUNC,如果不指定这个标志,在打开文件时,文件偏移量为文件开头处,但是并不清除文件的内容,接下来的写操作就会造成覆盖之前文件内容的情况,如果写入的字符数比文件中原有的字符数要多,那么也没什么问题,反正都覆盖了嘛,但如果写入的字符数比原来文件中的字符数要少,那么文件中没被覆盖的字符数就仍然存在。
接下来在看看open函数的O_CREAT和O_APPEND标志。做下面一个实验:首先在/tmp下只写打开文件b.txt,并删除其中内容,如果文件不存在就创建这个文件,然后向其中写入一行字符串"This is the first line.\n",然后关闭文件,然后以读写,追加的方式再次打开文件,然后从终端读取输入,追加写入文件中,直到读取到了'*'星号字符为止,最后将整个文件的内容输出。代码如下:
接下来在看看open函数的O_CREAT和O_APPEND标志。做下面一个实验:首先在/tmp下只写打开文件b.txt,并删除其中内容,如果文件不存在就创建这个文件,然后向其中写入一行字符串"This is the first line.\n",然后关闭文件,然后以读写,追加的方式再次打开文件,然后从终端读取输入,追加写入文件中,直到读取到了'*'星号字符为止,最后将整个文件的内容输出。代码如下:
void fileTest2() {
int fd = open("/tmp/b.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (-1 == fd) {
perror("open error");
return;
}
if (-1 == write(fd, "This is the first line.\n", sizeof("This is the first line.\n") - 1)) {
perror("write error");
return;
}
if (-1 == close(fd)) {
perror("close error");
return;
}
fd = open("/tmp/b.txt", O_RDWR | O_APPEND);
if (-1 == fd) {
perror("open error");
return;
}
char tmp;
printf("please input something, end with a '*'\n");
char str[20];
while('*' != (tmp = getchar())) {
str[0] = tmp;
if (-1 == write(fd, str, 1)) {
perror("write error");
return;
}
}
if (-1 == lseek(fd, 0, SEEK_SET)) {
perror("lseek error");
return;
}
int readsize;
do {
readsize = read(fd, str, 20);
if (-1 == readsize) {
perror("read error");
return;
}
if (-1 == write(1, str, readsize)) {
perror("write error");
return;
}
} while(0 != readsize);
close(fd);
}
O_APPEND标志等小于打开文件后执行lseek(fd, 0, SEEK_END);但两者仍有区别。O_APPEND标志使open和lseek两个操作成为一个原子操作,配合后面会试验的记录锁或进程互斥技术,可避免可能出现的多进程操作文件时的文件操作混乱。
O_CREAT标志表示如果没有这个文件,则创建之,此时要为open提供第三个参数,用于表示访问权限。
还有一个专门用于创建文件的系统调用——creat
#include <fcntl.h>
int creat(const char *path, mode_t mode);
返回值:若成功,返回为只写打开的文件描述符;若出错,返回-1
该函数等价于
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
入此看来,还是open创建文件方便一些,因为可以同时指定只写之外的读写权限。
说明:本文的实验使用eclipse建立工程,已上传至github:
https://github.com/haoranzeus/LinuxProgrammingLib.git
说明:本文的实验使用eclipse建立工程,已上传至github:
https://github.com/haoranzeus/LinuxProgrammingLib.git