【Hello Linux】基础IO(一)

简介: 【Hello Linux】基础IO

c语言中的文件操作

关于具体的c语言文件操作相关知识 大家可以参考博主之前写的两篇博客

c语言文件操作上

c语言文件操作下

下面我们会直接使用上两篇博客的知识写出两个文件读取操作的示例

文件写入

  1 #include <stdio.h>
  2 
  3 
  4 int main()
  5 {
  6   // 打开文件
  7   FILE* fp = fopen("log.txt" , "w");
  8   if (fp == NULL) // 打开失败返回空 报错
  9   {
 10     perror("fopen");
 11   }
 12   fputs("hello world!\n",fp); // fputs用法                                                                                 
 13   fclose(fp); // 关闭文件
 14   return 0;
 15 }

我们使用 “w” 的格式打开了一个叫做 log.txt 的文件

在这个格式下如果路径中没有该文件则会自动创建

创建完毕之后我们往文件中写入一段文本

我们编译之后看看效果

文件读取

在文件读取中我们用到一个很熟悉的函数 fgets

这个函数我们在之前写shell小程序的时候用来获取stdin中的信息

  1 #include <stdio.h>
  2
  3
  4 int main()
  5 {
  6   FILE* fp = fopen("log.txt" , "r"); // open the file 
  7   if (NULL == fp) // open fail
  8   {
  9     perror("fopen!");
 10   }
 11 
 12   char buffer[20];
 13   fgets(buffer , 20 , fp); // ¶ÁÈ¡ÎļþµÄÓ÷¨
 14   printf("%s",buffer);                                                                                                               
 15   return 0;
 16 }

我们使用 “r” 的格式打开了一个叫做log.txt的文件

在这个格式下我们只能对于打开的文件进行读取操作

编译运行之后看看效果

当前路径再理解

我们发现在上面向文件中写入内容的代码中 我们并没有指定文件的路径

系统默认在当前目录下给我们创建了一个新文件

那这时候我们猜想

系统默认会在我们当前所处的目录中给我们创建新文件

为了验证这个猜想我们首先退出到上层目录

然后在上层目录中执行lesson12的可执行程序

我们发现log.txt在我们当前目录下生成了

这也就验证了我们猜想的正确性

为什么会是这样子呢?

我们运行一个进程之后使用 ps 命令查看该进程的pid

之后进入该进程pid之后我们可以发现两个软连接

(软连接的概念我们这篇博客后面会讲解)

一个软连接是进程运行时的路径 cwd

一个软连接是可执行文件所在路径 exe

我们所说的当前路径就是cwd 即程序称为进程时候我们所在的路径

c语言中默认打开的三个流

一切皆文件

我们之间介绍过了一个概念叫做一切皆文件 所有的东西我们都可以当作文件来看待

比如说显示器可以当作一个文件 我们往显示器上打印数据实际上就是向这个文件中写入数据

比如说键盘可以当作一个文件 我们从键盘上获取数据实际上就是从这个文件中读取数据

c语言中三个流

我们在C语言中会默认打开三个流

他们分别是 stdin stdout stderr

他们对应的设备如下表

设备
stdin 键盘
stdout 显示器
stderr 显示器

我们在cplusplus中查询这三个流

当我们的c语言程序运行起来的时候 我们的程序会向操作系统申请这三个流之后我们的printf scanf等程序才可以向显示器打印数据 从键盘读取数据

从上面cplusplus的简介我们可以知道 其实这三个流的数据类型都是FILE*也就是文件类型

既然显示器我们可以当作一个文件 那么我们向这个文件中写入数据不就是向屏幕中打印数据嘛

我们实验下

1 #include <stdio.h>
  2 
  3 
  4 int main()
  5 {
  6   fputs("hello world!\n",stdout);
  7   fputs("hello linux!\n",stderr);                                                        
  8   return 0;
  9 }

编译之后运行

我们发现确实符合我们的预期

系统文件I/O

实际上不光是我们的c语言有这三个流 C++中也有类似的三个流 cin cout cerr 并且其他语言中也有类似的概念 所以说这并非是一个语言特有的而是操作系统所赋予共有的特性

接下来我们开始研究操作系统中的文件操作

首先来看下面这一张图

实际上我们语言层(c语言 c++)的函数都在用户调用接口这一层

而我们的系统接口函数则在系统调用接口这一层

也就是说其实我们语言层的函数是对于系统接口函数的封装

当我们在linux操作系统下运行c语言代码的时候 c语言的库函数就调用linux平台的系统调用接口进行封装 当我们在windows操作系统下运行c语言代码时 c语言的库函数就调用windows平台的系统调用接口进行封装

这样子我们的语言就具有了跨平台性 也可以二次开发了

open

我们在系统调用接口中一般使用open打开一个文件

它的函数原型如下

int open(const char *pathname, int flags, mode_t mode);

参数讲解

open的第一个参数

它的第一个参数要求我们输入一个字符串

  • 如果我们输入的字符串是一个路径 当需要我们创建文件时 文件会默认在这个路径下创建
  • 如果我们输入的字符串是一个文件名 当需要我们创建文件时 文件会默认在当前路径创建 (当前路径的概念已经在上面讲解)

open的第二个参数

它的第二个参数要求我们输入一个整数

而实际上我们在使用的时候并不会直接输入一个整数 而是会输入一系列的宏并且将它们进行位操作 这是因为我们并不是使用这个整数去标识打开的状态 而是使用这个整数的位去标识

举个例子(并不准确)

0000 0001 标识可写

0000 0010 表示追加

0000 0100 表示创建文件

如果我们想要这个文件是可写的状态打开 并且还可以追加 如果不存在就创建它 那么我们就需要使用按位或操作(|)

一般我们常用的选项如下表

参数选项 含义
O_RDONLY 以只读的方式打开文件
O_WRNOLY 以只写的方式打开文件
O_APPEND 以追加的方式打开文件
O_RDWR 以读写的方式打开文件
O_CREAT 当目标文件不存在时创建文件

就如我们上面所说 如果想要多种选项只需要按位或就好了

比如说想要以读写的方式打开文件并且当目标文件不存在时创建文件

我们就可以使用下面的格式

O_WRONLY | O_CREAT

open的第三个参数

它的第三个参数要求我们输入一个权限值

关于这部分的内容大家可以参考我的这篇博客

Linux权限的理解

注意: 如果我们不创建文件 则这个参数不需要填写

返回值讲解

open函数的返回值是一个整数 实际上这个整数就是一个文件描述符

  • 如果我们打开一个不存在的文件 open函数返回-1
  • 当我们打开一个文件之后open函数会返回给我们一个整数

我们下面来测试下上面的话对不对

1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 
  6 int main()
  7 {
  8   int fd = open("log1.txt" , O_RDONLY );
  9   printf ("%d\n" , fd);                                  
 10   return 0;
 11 }

当我们打开一个不存在的文件的时候我们编译运行代码

我们可以发现如果打开一个不存在的文件 返回值确实是-1

接下来我们试验下连续打开多个文件

1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 
  6 
  7 int main()
  8 {
  9   int fd1 = open("log1.txt" , O_RDWR | O_CREAT , 0666);
 10   int fd2 = open("log2.txt" , O_RDWR | O_CREAT , 0666);
 11   int fd3 = open("log3.txt" , O_RDWR | O_CREAT , 0666);
 12   int fd4 = open("log4.txt" , O_RDWR | O_CREAT , 0666);
 13   int fd5 = open("log5.txt" , O_RDWR | O_CREAT , 0666);
 14   int fd6 = open("log6.txt" , O_RDWR | O_CREAT , 0666);
 15   printf("%d\n",fd1);
 16   printf("%d\n",fd2);
 17   printf("%d\n",fd3);
 18   printf("%d\n",fd4);
 19   printf("%d\n",fd5);
 20   printf("%d\n",fd6);                                                                               
 21   return 0;
 22 }

编译运行之后查看下结果

运行之后我们发现打印的文件描述符是从3开始并且是连续的整数

看见这一堆数据之后我们脑海中会联想到什么?

是不是和数组的下标很像啊

实际上这里所谓的文件描述符本质上是一个指针数组的下标 指针数组当中的每一个指针都指向一个被打开文件的文件信息 通过对应文件的文件描述符就可以找到对应的文件信息

如果我们文件打开成功时 该数组中的指针个数便会增加 然后将该指针在数组中的下标返回

增加的规则是这样子的

如果前面的指针是从0开始连续的 那么新增的指针就会在原来的指针后面一个位置创建

如果前面的指针式从0开始不连续的 那么新增的指针就会找到缺失的第一个位置创建

如果我们的文件打开失败 则会返回-1 事实上数组下标也不存在-1

为什么文件描述符式从3开始的呢

还记不记得我们前面讲过 c语言程序会默认打开三个流 标准输入 标准输出 标准错误 实际上这三个流的底层就是打开了 0 1 2三个文件描述符

close

我们在系统接口层面使用close关闭文件 它的函数原型如下

int close(int fd);

它有一个参数 填入我们要关闭的文件描述符

它的返回值式一个整型 如果关闭成功则返回0 如果关闭失败则返回1

write

我们在系统接口中使用write函数像文件中写入文件 它的函数原型如下

ssize_t write(int fd, const void *buf, size_t count);

它的意思是像文件描述符为fd的文件中写入从buf开始count个字节的数据

参数

  • fd 我们要写入文件的文件描述符
  • buf 从buf这个位置开始读取数据写入
  • count 写入count个字节的数据

返回值

  • 如果数据写入成功 则返回实际写入的字节个数
  • 如果数据写入失败 则返回-1

代码示例

1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <string.h>
  5 #include <fcntl.h>
  6 #include <unistd.h>
  7 
  8 int main()
  9 {
 10   // 打开文件
 11   int fd = open("log.txt",O_CREAT | O_RDWR , 0666);
 12 
 13   // 写入数据
 14   const char* tmp = "hello world\n";
 15   write(fd , tmp , strlen(tmp));
 16   close(fd);
 17   return 0;                                                                          
 18 }

这个代码的意思是 以读写的权限打开一个叫做log.txt的文件 如果这个文件不存在就创建它 设置初始权限为0666

之后我们像这个文件中写入tmp里面的数据

结果演示

我们发现数据确实被写入到了文件当中

read

我们在系统接口中使用read函数像文件中读取文件 它的函数原型如下

ssize_t read(int fd, void *buf, size_t count);

它的意思是从文件描述符为fd的文件中读取conut个字节的数据存放到buf中

参数

  • fd 我们要读取文件的文件描述符
  • buf 存放数据的地址
  • count 读取的字节数

返回值

  • 如果数据读取成功 实际读取数据的字节个数被返回
  • 如果数据读取失败 返回-1

代码示例

#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <string.h>    
#include <fcntl.h>    
#include <unistd.h>    
int main()    
{    
  // 打开一个文件    
  int fd = open("log.txt" , O_RDONLY);    
  if (fd < 0)    
  {    
    perror("open");    
  }    
  char buf[20];    
  read(fd , buf , 20);                                                                                                         
  printf("%s",buf);    
  return 0;    
} 

我们这段代码中 使用只读的方式打开了log.txt文件

接着我们使用了一个缓冲区buf来接受文件中的内容

最后我们打印缓冲区buf里面的字符串

结果演示

我们发现文件中的数据被打印出来了

相关文章
|
3月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
104 0
|
15天前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
181 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
3月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
122 1
Linux C/C++之IO多路复用(aio)
|
7天前
|
Linux API C语言
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
35 14
|
5月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
3月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
47 0
Linux C/C++之IO多路复用(poll,epoll)
|
5月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
39 0
|
5月前
|
存储 IDE Linux
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
|
6月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
86 0
|
6月前
|
Linux 编译器 C语言
【Linux】基础IO----理解缓冲区
【Linux】基础IO----理解缓冲区
86 0
【Linux】基础IO----理解缓冲区