Linux基础IO
文章目录
- Linux基础IO
- C语言中的文件操作
- c语言文件打开方式
- C语言文件操作函数
- 系统文件操作
- stdin/stdout/stderr
- opean
- close
- write
- read
- 文件描述符
- 重定向
- 什么是重定向
- dup2
C语言中的文件操作
我们通过两个代码复习一下C语言中的文件操作:
int main()
{
FILE* fp = fopen("log.txt", "w");
if(fp == nullptr) perror("fopen fail");
fputs("hello world\n", fp);
fclose(fp);
return 0;
}
int main()
{
FILE* fp = fopen("log.txt", "r");
if(fp == nullptr) perror("fopen fail");
char buffer[64];
fgets(buffer, sizeof(buffer), fp);
cout << buffer << endl;
return 0;
}
第一个是文件的写操作,用w
的格式打开一个文件进行写入。
第二个是文件的读操作,用r
的格式打开一个文件进行读取。
c语言文件打开方式
r
,以只读的方式打开文件,文件指针指向文件的首位置。r+
,以读写的方式打开文件,文件指针指向文件的首位置。w
,以只写的方式打开文件,清空文件内容,如果文件不存在就创建一个文件,文件指针指向文件的首位置。w+
,以读写的方式打开文件,如果文件不存在就创建一个文件,如果文件存在就清空文件内容,文件指针指向文件的首位置。a
,以追加写的方式打开文件,将内容追加在文件末尾,如果文件不存在就创建一个文件,文件指针指向文件的末尾位置。a+
,以读和写的方式打开,将内容追加到末尾位置,如果文件不存在就创建一个文件,文件指针指向文件的末尾位置。
C语言文件操作函数
- fopen
FILE* fopen(const char* path, const char* mode)
- path:要打开的文件
- mode:文件打开方式
- 返回值:成功,返回文件流指针;失败,返回NULL
- fread
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream)
- ptr:将从文件读到的内容保存在ptr指向的空间中
- size:读文件时,一个块的大小,单位是字节
- nmemb:期望读到的字节大小
- stream:文件流指针
- 返回值:读到的块的个数
一般我们将块大小定义为1字节,返回值就是我们读了多少字节。
例如:
int main()
{
FILE *fp = fopen("log.txt", "r+");
if (fp == nullptr)
perror("fopen fail");
fputs("hello world\n", fp);
fseek(fp, 0, SEEK_SET);
char buffer[64];
memset(buffer, 0, 64);
fread(buffer, sizeof(char), sizeof buffer, fp);
printf("%s\n", buffer);
fclose(fp);
return 0;
}
- fwrite
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream)
- ptr:准备写入的内容
- size:向文件写入块的大小,单位大小为字节
- nmemb:期望写的块的大小
- stream:文件流指针
- 返回值:返回成功写入的块的个数
int main()
{
FILE *fp = fopen("log.txt", "r+");
if (fp == nullptr)
perror("fopen fail");
const char* buffer = "xxxxxxxxxxxxxxxxxx\n";
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
return 0;
}
- fseek
int fseek(FILE* stream, long offser, int whence)
- stream:文件流指针
- offset:偏移量
- whence:文件指针偏移到的位置。SEEK_SET,把文件流指针定义到文件头部。
SEEK_CUR,把文件流指针定义到当前文件位置。SEEK_END,把文件流指针定义到文件末尾。
- ftell
long ftell(FILE* stream)
- stream:文件流指针
- 作用:获取文件指针当前位置
- rewind
void rewind(FILE* stream)
-
stream:文件流指针
-
作用:让文件指针回到文件起始位置
- fclose
int fclose(FILE* fp)
- 作用关闭文件
系统文件操作
在学校系统文件操作前,有这样一个问题需要补充:为什么我们向显示器输出数据,为什么不需要进行打开显示器操作呢?为什么可以直接使用键盘向电脑输入数据呢?
stdin/stdout/stderr
有这样一句话,Linux下一切皆文件,也就是说,在Linux系统下所有东西都可以看做文件。
系统会默认帮我们打开三个流:标准输入流0、标准输出流1和标准错误流2。
它们分别对应的外设是:键盘,显示器和显示器。
它们对应到C语言中就是:stdin、stdout和stderr。
当我们的C程序跑起来时,操作系统就会默认使用C语言的相关接口将这三个输入输出流打开,之后我们才能调用类似于scanf和printf之类的函数向键盘和显示器进行相应的输入输出操作。
我们还可以查到,它们三个都是FILE*类型(和我们之前说的Linux下一切皆文件相对应)。
opean
int open(const char *pathname, int flags, mode_t mode);
- pathname:表示要打开或创建的目标文件。
以路径给出,创建该文件时,在给出的路径下创建。
以文件名给出,创建该文件时,在当前路径下创建。
- flags:表示文件的打开方式。
O_RDONLY:以只读的方式打开
O_WRNOLY:以只写的方式打开
O_APPEND:以追加的方式打开
O_RDWR:以读写的方式打开
O_CREAT:当文件不存在时,创建该文件
- mode:表示创建文件的默认权限。
例如:设置读写权限0666(具体可以查看之前的文章Linux权限管理)
若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask
函数将文件默认掩码设置为0。
int main()
{
umask(0);
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
return 0;
}
- 返回值:失败则返回-1。
我们也可以看看文件描述符到底是不是从3开始的。(0,1,2默认打开)
int main()
{
umask(0);
int fd1 = open("log.txt", O_RDWR | O_CREAT, 0666);
int fd2 = open("log.txt", O_RDWR | O_CREAT, 0666);
int fd3 = open("log.txt", O_RDWR | O_CREAT, 0666);
int fd4 = open("log.txt", O_RDWR | O_CREAT, 0666);
cout << fd1 << endl << fd2 << endl << fd3 << endl << fd4 << endl;
return 0;
}
close
int close(int fd);
作用关闭文件。
- fd : 文件描述符
write
ssize_t write(int fd, const void *buf, size_t count);
作用:向文件写入信息。
-
fd:文件描述符
-
buf:需要写入内容的起始位置
-
count:需要写入的字节数
-
返回值:写入成功,返回实际写入的字节数;写入失败,返回-1。
示例:
int main()
{
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
if(fd < 0) perror("open fail");
char buffer1[64] = "hello world\n";
write(fd, buffer1, strlen(buffer1));
close(fd);
return 0;
}
read
ssize_t read(int fd, void *buf, size_t count);
作用:读取文件中的信息。
-
fd:文件描述符
-
buf:需要读取内容的起始地址
-
count:需要读取的字节数
-
返回值:读取成功,返回实际读取字节数;读取失败,返回-1。
示例:
int main()
{
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
if(fd < 0) perror("open fail");
char buffer2[64];
read(fd, buffer2, sizeof(buffer2));
cout << buffer2 << endl;
close(fd);
return 0;
}
文件描述符
我们都知道进程创建时同时会创建PCB、mm_struct和页表。其中task_struct中有一个指针,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。
我们我们运行进程打开log.txt文件的时候 我们首先会将文件从磁盘加载到内存当中 并且形成对应的struct file结构体并且连入双链表中然后在file_array中找到一个未被使用的空间 让这个空间指向我们刚刚的struct file结构体。
最后将这个空间的下标返回给进程。所以说只要我们有一个文件的fd就能够对这个文件进行各种操作。
我们之前也测试过了,文件描述符是从3开始的。
原因是:它是从file_array中找到未使用空间的最小下标开始分配,其中0、1、2已经在程序运行时自动打开了。
重定向
什么是重定向
重定向是把原本输出到一个文件的数据输出到另一个文件。
例如:
int main()
{
close(1);
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
if(fd < 0) perror("open fail");
fputs("hello worldxxxxxxxxxxxxxxxx\n", stdout);
cout << endl;//刷新缓冲区
close(fd);
return 0;
}
由于我们一开始关闭了stdout(1),我们把原本输出到stdout文件的输出到了log.txt文件中,所以显示器无法打印信息了,必须从log.txt中查看。
这就是一个重定向,我们也可以使用以下命令完成重定向:
- 清空重定向
[---@VM-8-4-centos day03]$ echo "xxxxxxxxxxxxxxxxx" > log.txt
[---@VM-8-4-centos day03]$ cat log.txt
- 追加重定向
[---@VM-8-4-centos day03]$ echo "yyyyyyyyyyyyyyyy" >> log.txt
[---@VM-8-4-centos day03]$ cat log.txt
dup2
在上面使用重定向时需要首先使用close关闭文件,这样我们感觉总是十分麻烦。系统给我们提供了一个便捷的系统调用:
int dup2(int oldfd, int newfd);
- oldfd:旧的文件描述符
- newfd:新的文件描述符
- 返回值:调用成功,返回newfd;调用失败,返回-1。
功能:将oldfd中的内容拷贝到newfd中。
ps:如果newfd和oldfd相同,则不做任何操作,返回newfd。
例如:
int main()
{
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0) perror("open fail");
// close(1);
dup2(fd, 1);
printf("dup2 success\n");
return 0;
}
感谢您的阅读,欢迎指正。