Linux基础IO【文件操作】

简介: Linux文件的理解与操作,关于C语言的文件操作回顾以及系统级文件操作接口的介绍,详细讲解,干货满满!

Linux基础IO【文件操作】

前面讲过C语言的文件操作,文件操作是基础IO的重要部分,下面我们就来学习一下Linux中的文件操作

1. 文件认识

先来讲述对于文件的几个疑问

文件是怎么构成的,放在哪里?

  • 文件 = 内容 + 属性
  • 文件被分为两大类:磁盘文件、内存文件(被打开的文件)
  • 文件没有被操作的时候,一般放在磁盘上
  • 文件被操作的时候,文件属性会被加载到内存中,冯诺依曼体系规定

文件操作的本质是什么?

  • 对文件的操作无非就两种:对内容进行操作或者对属性进行操作,这里讲述后者
  • 文件操作本质就是将需要的文件属性加载到内存中,操作系统一定同时存在大量的被打开的文件,同时操作系统也要管理这些被打开的文件,通过先描述再组织的方式
  • 先描述就是构建在内存中的文件结构体struct file,来存储文件属性进行管理,这个结构体可以从磁盘上拿到,再组织就是通过数据结构来组织,比如:链表来连接结构体节点

文件是谁打开的?

  • 文件是被操作系统打开的,是由用户创建进程,进程让操作系统完成打开文件的任务

所有语言的文件操作,本质上都是调用系统级接口进行操作,要针对底层系统级文件操作进行学习

2. 回顾C文件接口

先来回顾一下C语言的文件操作

2.1 打开文件

使用fopen函数打开文件

FILE * fopen ( const char * filename, const char * mode );

fopen函数

  • 返回值:打开文件失败返回NULL

  • filename参数:要打开的文件名,直接使用文件名,此文件需要位于当前程序目录下,可以使用绝对路径来指定目录存放

  • mode参数:文件打开方式
    • w:只写,文件写入前会先清空文件原内容,如果文件不存在会自动创建它
    • a:追加,在文件末尾对文件追加写入内容,不会清空原内容
    • r:只读,打开指定文件进行读取操作,如果文件不存在则会打开失败
    • w+a+r+:可读可写、可读可追加、可读可写,其中只有r+不会自动创建文件

2.2 关闭文件

文件打开使用完后需要关闭,使用fclose函数关闭文件

int fclose ( FILE * stream );

FILE*指针进行操作,只能关闭已打开的文件,文件不存在会报错

FILE* fd = fopen("text.txt", "w"); //打开文件

//...文件使用...

fclose(fd);  //关闭文件

2.3 文件写入

C语言中的文件写入方式

int fputc ( int character, FILE * stream );  //逐字符写入

int fputs ( const char * str, FILE * stream );  //逐行写入

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );  //二进制格式化写入

int fprintf ( FILE * stream, const char * format, ... );  //格式化写入

int snprintf ( char * s, size_t n, const char * format, ... );

前几个都比较简单,来讲解一下snprintf

  • s参数:缓冲区
  • n参数:缓冲区大小
  • format参数:格式化输入

使用snprintf函数将数据写到缓冲区后,可以通过fputs函数将缓冲区中的数据写入文件中

#include <stdio.h>

#define TEXT "text.txt"

int main()
{
   
   
  FILE* fd = fopen(TEXT,"w");
  if(fd == NULL)
  {
   
   
    perror("fopen fail!");
    exit(-1);
  }

  const char* message = "hello world";

  char buffer[256]; //缓冲区
  int n = 5;
  while(n--)
  {
   
   

    snprintf(buffer, sizeof(buffer), "%s %d\n", message, n); //向缓冲区写入内容
    fputs(buffer, fd); //将缓冲区中的内容写入文件
  }

  fclose(fd);
  return 0;
}

2.4 文件读取

C语言中的文件读取方式

int fgetc ( FILE * stream );

char * fgets ( char * str, int num, FILE * stream );

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

int fscanf ( FILE * stream, const char * format, ... );

int sscanf ( const char * s, const char * format, ...);

这里的sscanf用于按照一定规则格式化读取字符串

#include <stdio.h>

int main()
{
   
   
  char s[] = "2023,7,20";
  int arr[3];
  char* buffer[4];

  sscanf(s, "%d,%d,%d", arr, arr + 1, arr + 2);
  printf("%d %d %d\n", arr[0], arr[1], arr[2]);
  return 0;
}

更多C语言文件操作细节可以查看文章《C语言文件操作详解》

3. 系统文件操作接口

3.1 打开文件open

系统级打开文件接口open

#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);

open函数讲解

  • 返回值:成功返回新打开的文件描述符,失败返回-1

  • pathname参数:待操作文件名

  • flags参数:打开文件时,可以传入多个参数选项构成
  • mode参数:权限设置,文件默认权限为0666

讲解一下flags参数

  • flags是一个int类型的变量,它有32个比特位,这里选项有很多,如O_APPENDO_CREATO_TRUNC
  • 32个比特位每一个都可以表示一个标志位,这样就有32个标志位,也就是对应的可以有32个选项。这种数据结构就是是位图,使用位图进行多参数传递

知道了特性,下面用一个小demo来展示一下位图

#include <stdio.h>

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4

//模拟实现选项传递
void out(int flags)
{
   
   
  if(flags & ONE) printf("1111\n");
  if(flags & TWO) printf("2222\n");
  if(flags & THREE) printf("3333\n");
}

int main()
{
   
    
  out(ONE);
  printf("-----------\n");
  out(TWO);
  printf("-----------\n");
  out(ONE | TWO);
  printf("-----------\n");
  out(ONE | TWO | THREE);
  printf("-----------\n");
  return 0;
}

每个比特位来设置一个选项,可以直接通过传入参数的形式执行不同的功能

列举几个open系统调用中的flags选项

O_APPEND  //文件用追加的方式被打开
O_CREAT   //如果文件不存在就会自动创建
O_RDONLY  //只读模式
O_WRONLY  //只写模式
O_TRUNC   //清理文件

试用一下

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT "text.txt"

int main()
{
   
   
  //两种参数组合体现fopen中的a选项
  int fd = open(TEXT, O_CREAT | O_WRONLY, 0666); //权限最好加上
  if(fd == -1)
  {
   
   
    perror("open fail1");
    return;
  }
  else 
  {
   
   
    printf("fd: %d, errno: %d, errstring: %s\n",fd, errno, strerror(errno));
  }

  close(fd);
  return 0;
}

这里的mode是权限,有以下几种情况

S_IRWXU  00700 user (file owner) has read, write and execute permission
S_IRUSR  00400 user has read permission
S_IWUSR  00200 user has write permission
S_IXUSR  00100 user has execute permission
S_IRWXG  00070 group has read, write and execute permission
S_IRGRP  00040 group has read permission
S_IWGRP  00020 group has write permission
S_IXGRP  00010 group has execute permission
S_IRWXO  00007 others have read, write and execute permission
S_IROTH  00004 others have read permission
S_IWOTH  00002 others have write permission
S_IXOTH  00001 others have execute permission

注意:

  • 权限可以直接以数字的形式给出
  • 若文件不存在,mode参数最好设置,不然的话创建出的文件权限是随机值
  • umask默认为0002,可以自定义

3.2 关闭文件 close

系统级关闭文件接口close,它是根据文件描述符来关闭文件的

#include <unistd.h>

int close(int fildes);

Linux下的三个标准流为:stdinstdoutstderr,分别对应的文件描述符为:012,可以通过close(1)来关闭标准流

3.3 文件写入 write

系统级文件写入接口write

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);

使用方法与fwrite基本相同,返回值有所差异:成功返回写入的字节总数,失败返回-1并且errno被设置

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT "text.txt"

int main()
{
   
   
  //三种参数组合体现fopen中的w选择
  int fd = open(TEXT, O_WRONLY | O_CREAT | O_TRUNC, 0666);    
  if(fd == -1)
  {
   
   
    perror("open fail1");
    return;
  }
  else 
  {
   
   
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }

  const char* message = "hello world\n";
  int count = 5;
  while(count--)
  {
   
   
    write(fd, message, strlen(message)); //这里不能将'\0'写入文件中
  }

  close(fd);
  return 0;
}

注意:使用write写入字符串时,不要加上 '\0'当作结尾,因为对于系统来说,'\0'也是个普通的字符,'\0' 作为字符串结尾只是 C语言 的规定

3.4 文件读取 read

系统级文件读取接口read

#include <unistd.h>

ssize_t read(int fildes, void *buf, size_t nbyte);

read函数在读取文件时,也是借助缓冲区进行读取,不过只支持按指定字符数读取,无法按行读取,返回值和write相同

//读取text.txt中内容
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT "text.txt"

int main()
{
   
   
  int fd = open(TEXT, O_RDONLY, 0664);
  if(fd == -1)
  {
   
   
    perror("open fail1");
    return;
  }

  char buffer[256]; //缓冲区
  size_t ret = read(fd, buffer, sizeof(buffer) - 1);
  if(ret > 0)
  {
   
   
    buffer[ret] = '\0';
    printf("%s\n", buffer);
  }

  close(fd);
  return 0;
}

//读取myfile.c中的66个字符
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT "myfile.c"

int main()
{
   
   
  int fd = open(TEXT, O_RDONLY, 0664);
  if(fd == -1)
  {
   
   
    perror("open fail1");
    return;
  }

  int n = 66; //要读取的字符数
  char buffer[256];
  int pos = 0;
  while(n--)
  {
   
   
    read(fd, (char*)buffer + pos, 1);
    pos++;
  }

  printf("%s\n", buffer);

  close(fd);
  return 0;
}

4. 系统调用和库函数

我们上面回顾了C语言的文件操作库函数,讲解了文件操作系统级的接口,在Linux下,无论是什么语言,在进行文件操作时所使用的函数,本质上都是对系统调用接口的封装,在文件操作中,是无法直接与硬件(磁盘)交互的,必须经过系统调用接口到操作系统再到驱动程序这一途径


Linux基础IO【文件操作】,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正

目录
相关文章
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
315 0
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
584 1
Linux C/C++之IO多路复用(aio)
|
5月前
|
Linux C语言 网络架构
Linux的基础IO内容补充-FILE
而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fwrite函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和fwrite函数打印的数据就有两份。此时我们就可以知道,
88 0
|
5月前
|
存储 Linux Shell
Linux的基础IO
那么,这里我们温习一下操作系统的概念我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发。这就是因为在根本上操作系统确实像银行一样,并不完全信任用户程序,因为直接开放底层资源(如内存、磁盘、硬件访问权限)给用户程序会带来巨大的风险。所以就向银行一样他的服务是由工作人员隔着一层玻璃,然后对顾客进行服务的。
65 0
|
9月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
363 34
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
11月前
|
Linux API C语言
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
227 14
|
12月前
|
运维 监控 网络协议
运维工程师日常工作中最常用的20个Linux命令,涵盖文件操作、目录管理、权限设置、系统监控等方面
本文介绍了运维工程师日常工作中最常用的20个Linux命令,涵盖文件操作、目录管理、权限设置、系统监控等方面,旨在帮助读者提高工作效率。从基本的文件查看与编辑,到高级的网络配置与安全管理,这些命令是运维工作中的必备工具。
873 3
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
388 0
Linux C/C++之IO多路复用(poll,epoll)