1. open函数和close函数
#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);#include <unistd.h>
int close(int fd);
open
函数是一个用于打开或创建文件的系统调用,C语言的fopen
函数就是对该函数的封装。
pathname
:要打开或创建的文件的路径名(相对路径或绝对路径)。flags
:控制文件打开模式的标志,如只读、只写、读写、创建等。mode
:当创建新文件时,指定文件的权限。这个参数是可选的,仅当在flags
中设置了O_CREAT
标志时使用。
close函数对应的自然就是fclose函数,将open
函数的返回值作为参数传入即可关闭文件,就不过多解释了,重要的是open
函数的使用。
1.1 flag参数
该参数是一个整型值,通过一系列的宏来传递参数,常用的有:
O_RDONLY
:只读模式打开。O_WRONLY
:只写模式打开。O_RDWR
:读写模式打开。O_CREAT
:如果文件不存在,则创建它。使用这个选项时,需要提供第三个参数mode
,指定新文件的权限。O_EXCL
:与O_CREAT
一起使用时,如果文件已存在,则返回错误。这用于测试文件是否存在。O_TRUNC
:如果文件已存在且为普通文件且以只写/读写方式打开,则其长度被截断为0。O_APPEND
:写入时将数据追加到文件末尾。
flags本质上是一个位图,而这些宏本质上都是只有一个比特位为1的整型值。
这样的设计方式,使得我们可以利用flags同时传递多条信息,例如:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("test.txt", O_CREAT | O_WRONLY | O_TRUNC);close(fd);return 0;
}// open函数内部判定某个flag是否被选用的原理如下
if(flags & O_CREAT)
{// ...
}
if(flags & O_WRONLY)
{// ...
}
if(flags & O_TRUNC)
// ...
1.2 mode参数
mode
参数指定新创建文件的权限(只有当flags参数中包含O_CREAT时使用),通常与umask
的设置相结合来确定最终权限。
一些常见的权限包括:
S_IRUSR
、S_IWUSR
、S_IXUSR
:分别表示文件所有者的读、写、执行权限。S_IRGRP
、S_IWGRP
、S_IXGRP
:分别表示用户组的读、写、执行权限。S_IROTH
、S_IWOTH
、S_IXOTH
:分别表示其他用户的读、写、执行权限。
传参的原理和方式与flags相同,但是使用上面的这些宏来传参太过麻烦了,这些宏的数值实际上是有规律的,分别与权限的八进制表示相对应。
不清楚权限的八进制表示方式的小伙伴可以参考我的这篇博文:Linux笔记---Linux权限理解_linxu用户权限的笔记-CSDN博客
例如,假设"test.txt"在当前目录下不存在,我们希望在创建该文件时将文件的读写权限全部放开:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);close(fd);// 等价于// FILE* fp = fopen("test.txt", "w");// fclose(fp);return 0;
}
open函数新建文件时,如果我们没有传入mode,那么新文件的权限就会显示为乱码:
1.3 文件描述符
open函数的返回值是对应文件的文件描述符,即当前程序中已打开的文件在管理文件指针的数组(file* fd_array[])中的下标,从0开始依次递增。
但由于程序在启动时会默认打开stdin、stdout、stderr,所以我们自己打开的文件的文件描述符是从3开始递增的。
分配下标的规则也很简单:当有文件被打开时,依次遍历fd_array数组,找到第一个为空的下标。
假如我们将1号文件关闭,再另打开一个文件,那么这个文件就会占据1号下标,同时被各种库函数当作标准输出:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>int main()
{close(1);int fd = open("test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd == 1)printf("喜欢stdout的小朋友你们好啊!我是test.txt,stdout已经被我干掉了!\n");fprintf(stdout, "fd: %d\n", fd);return 0;
}
可以看到,printf函数将内容输出到了test.txt而不是标准输出。
2. write函数和read函数
2.1 write函数
write
函数用于向文件描述符中写入数据。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd
:文件描述符,表示要写入的文件、管道、套接字等。buf
:指向要写入数据的缓冲区的指针。count
:要写入的数据的字节数。
write
函数的返回值为实际写入的字节数,如果发生错误则返回 -1,并设置errno
来指示错误原因。
2.2 read函数
read
函数用于从文件描述符中读取数据。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd
:文件描述符,代表了需要读取的文件或设备。buf
:一个指向用户分配的缓冲区的指针,read
函数将把读取到的数据写入该缓冲区。count
:需要读取的字节数,表示最多读取count
字节数据。
read
函数的返回值为实际读取到的字节数,如果到达文件末尾则返回0,如果发生错误则返回 -1,并设置errno
来指示错误原因。
2.3 示例代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>int main() {int fd;char buffer[1024];// 打开文件fd = open("test.txt", O_RDWR | O_CREAT, 0666);if (fd == -1) {perror("open");return 1;}// 写入数据const char *data = "Hello, World!";ssize_t bytes_written = write(fd, data, strlen(data));if (bytes_written == -1) {perror("write");close(fd);return 1;}// 移动文件指针到文件开头lseek(fd, 0, SEEK_SET);// 读取数据ssize_t bytes_read = read(fd, buffer, sizeof(buffer));if (bytes_read == -1) {perror("read");close(fd);return 1;}// 输出读取的数据buffer[bytes_read] = '\0';printf("Read: %s\n", buffer);// 关闭文件close(fd);return 0;
}
首先使用open
函数以读写方式打开一个文件,如果文件不存在则创建它。然后使用write
函数向文件中写入数据,接着使用lseek
函数将文件指针移动到文件开头,再使用read
函数从文件中读取数据,最后关闭文件。
3. 重定向
我们在命令行当中可以使用">"、">>"、"<"来进行重定向,即将一个程序的结果输出到文件中,或者从一个文件中读取程序的输入。
重定向的类型
- [>]输出重定向:将本来应该输出到标准输出(终端屏幕)的数据重定向到一个文件中。
- [<]输入重定向:将本来应该从标准输入(键盘)读取的数据重定向到一个文件中。
- [>>]追加重定向:将数据追加到一个文件的末尾,而不是覆盖文件的内容。
那么,重定向的本质是什么呢?
还记得我们在文件描述符那里举得例子吗?这就是重定向的本质,即修改文件描述符所指向的内容,也是我们在代码层面进行重定向的方式之一。
但是,利用下标分配的机制来进行重定向未免有点繁琐,而且不够精确,可读性,可维护性都较差。
在代码层面,我们常用的重定向的方式是使用dup2系统调用。
dup2函数
int dup2(int oldfd, int newfd);
它的作用是将oldfd
所指的文件描述符复制到newfd
,并且返回newfd
。
如果newfd
已经打开,则先关闭newfd
,再进行复制。该函数成功时返回新的文件描述符,失败时返回 -1,并设置相应的错误码。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("test.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);dup2(fd, 1);printf("喜欢stdout的小朋友你们好啊!我是test.txt,stdout已经被我干掉了,这条消息不会写到标准输出啦!\n");return 0;
}