【Linux】---文件基础I/O

2023-11-14

回顾C语言文件操作接口

在C语言中对于文件的操作有着几个常用的接口可以调用

fopen//打开文件
fclose//关闭文件
fprintf//输出
fscanf//输入

还有相对应不同功能的打开方式

关于各接口的详细介绍可以看看博主之前写的博客 C语言文件操作

文件相关的系统调用接口

事实上每一门高级语言都有其对应的文件操作接口,而它们所拥有的接口都是对操作系统本身对文件操作接口的封装,所以只要我们掌握了操作系统本身的文件操作接口那对于之后无论是学习哪一门高级语言的接口都可以很大程度的降低学习成本。

打开和关闭文件

首先对文件进行操作肯定要先打开或关闭某一个文件,在Linux中对应的接口为

open//打开文件
close//关闭文件

image-20221223085853508

open的第一个参数就是文件名,第二个参数是文件的打开方式,第三个是文件新建时的权限。

当文件打开成功时返回一个大于0的整数(文件描述符 下面会讲到),文件打开失败时返回-1

image-20221223091307974

关闭文件只需要传入文件成功打开时的返回值

文件的打开方式

对于Linux系统的文件打开方式也是一样,可以看到flags是一个int类型,操作系统内部对各个打开方式进行了宏定义

O_RDONLY //只读打开
O_WRONLY //只写打开
O_RDWR //读写打开
//以上三个常亮,必须且只能指定一个

O_CREAT //若文件不存在则创建文件
O_APPEND //追加写入

如果想要多个选项一起,那就使用 | 运算符即可。

对于第三个参数 文件的权限,就是当指定打开的文件不存在时新建该文件后其对应的权限。如果新建出来的文件我们没有给定第三个参数那么文件的权限就会随机。一般而言普通文件的权限都是 0666 新建的,所以当我们不知道需要打开的文件是否存在时最好是在调用 open 时加上第三个参数。

下面看一段代码感受一下

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

int main(){
    //以只读且不存在创建的方式打开文件,新建文件的初始权限为0666
    int fd = open("log.txt", O_RDONLY | O_CREAT, 0666);
   	
    //判断是否打开成功
    if(fd < 0){
        perror("open");
        return 1;
    }
    
    //关闭文件
    close(fd);
    
    return 0;
}

image-20221223091832015

可以看到一开始并没有 log.txt这个文件,执行了程序后就创建出了一个新的且权限为 0666 的文件。

文件描述符

上面提到,当文件成功打开时返回文件的描述符,那么什么是文件描述符呢。

对文件的操作实际上是进程去完成的,一个进程可以打开多个文件。既然是进程去完成的操作,那么根据之前的学习我们知道操作系统对进程都是需要管理的,也就是操作系统会对进程进行 先组织再描述。那么操作系统为了可以管理起被打开的文件就一定会为其创建相对应的内核数据结构用来描述。在Linux中,通过

struct file{

}

描述每一个被打开的文件。*为了使进程能够执行open系统调用,所以必须让进程和文件关联起来,因此每个进程都会有一个 file指针去指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针。因此 文件描述符本质上就是该数组的下标。所以只需要拿到下标就可以找得到对应的文件

文件描述符的分配规则

既然我们知道了文件描述符的意义,那么接下来就看看文件描述符是如何分配的,先来看看同时打开多个文件,它们所对应的文件描述符是什么

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

int main(){
    //以只读且不存在创建的方式打开文件,新建文件的初始权限为0666
    int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666);
    int fd2 = open("log2.txt", O_RDONLY | O_CREAT, 0666);
    int fd3 = open("log3.txt", O_RDONLY | O_CREAT, 0666);
    int fd4 = open("log4.txt", O_RDONLY | O_CREAT, 0666);
   	
    printf("%d\n", fd1);
    printf("%d\n", fd2);
    printf("%d\n", fd3);
    printf("%d\n", fd4);
    
    //关闭文件
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    
    return 0;
}

image-20221223094359994

可以看到它们的文件描述符是有序的,那么既然是下标为什么不从0先开始呢。事实上,Linux进程默认情况下会有3个缺省打开的文件描述符,分别是:

0 //标准输入 -> 键盘
1 //标准输出 -> 显示器
2 //标准错误 -> 显示器

因此每一个被打开的文件的文件描述符都是从3依次记录的。相对应的如果我们将0、1、2关闭后再打开那么该文件的文件描述符就会从最小没有被占用的下标开始

write、read

认识了文件的打开关闭和文件描述符后,我们再来看看系统中对文件操作的其他接口。

image-20221223095343939

write 是往文件里写入,第一个参数为文件描述符,第二个为指向需要写入数据,第三个为需要写入的字节数。

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

int main(){
    int fd  = open("log.txt", O_WRONLY | O_CREAT, 0666);
    
    char* buf = "hello world!";
    write(fd, buf, strlen(buf));
    
    close(fd);
    
    return 0;
}

image-20221223095716846

image-20221223095736760

read和write的调用是一样的。

重定向

既然可以关闭0、1、2的文件,那么现在我们关闭1试试看

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

int main(){
    close(1);
    
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    printf("%d\n", fd);
    fflush(stdout);
    
    close(fd);
    
    return 0;
}

image-20221223102631980

为什么执行之后没有显示出文件的描述符呢。这是因为文件描述符1虽然一开始是指向输出显示器的,但是当我们关闭之后再打开文件,这是1就不再是指向标准输出了而是指向了新打开的文件,所以也就不再输出到屏幕而是输出到了文件中。这就是输出重定向

那么重定向的本质是什么呢

没有发生重定向前呢,可以画图概括为

image-20221223103449480

而当我们先关闭了1之后,1就不再指向标准输出了,再打开一个新的文件那么1就指向了新打开的文件

image-20221223103704372

也就是说,重定向的本质就是:上层用的文件描述符不变,在内核中嘎变了文件描述符对应的 struct file* 的地址

系统中也有对应的重定向接口,不需要我们每次都关闭某一个文件再打开新文件。

dup2

image-20221223104118194

这个接口就可以直接实现文件的重定向,使用该接口后,重定向的文件描述符就会指向打开的文件。

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

int main(){
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    dup2(fd, 1);
    printf("%d\n", fd);
    fflush(stdout);
    
    close(fd);
    
    return 0;
}

mysell

根据这些新学的知识,就可以给之前写的shell进行功能增加了

> 输出重定向
>> 追加
< 输入重定向
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>
#include<assert.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#include<errno.h>

#define NUM 1024
#define OPT_NUM 100

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

//记录打开文件的规则
int redirType = NONE_REDIR;
//记录要打开的文件名
char* redirFile = NULL;

//记录输入字符串
char lineCommand[NUM];
//指针数组用于记录输入字符串的指令(不含选项)
char *myargv[OPT_NUM];
//记录退出状态和终止信号
int  lastCode = 0;
int  lastSig = 0;

//定义跳过空格函数
void trimSpace(char* start){
  while(1){
    if(*start == ' ')
      start++;
    else
      break;
  }
}

//定义重定向指令的分割方法
//将重定向的文件名获取
void commandCheck(char* com){
  assert(com);

  char* start  = com;
  char* end = com + strlen(com);

  while(start < end){
    if(*start == '>'){
      *start = '\0';
      start++;

      //如果有两个> 说明是重定向追加输出
      if(*start == '>'){
        redirType = APPEND_REDIR;
        start++;
      }

      else
        redirType = OUTPUT_REDIR;

      //需要将重定向符号后面的空格都跳过才可获取到文件名
      trimSpace(start);
      redirFile = start;
      break;
    }
    
    else if(*start == '<'){
      *start = '\0';
      start++;

      trimSpace(start);
      redirType = INPUT_REDIR;
      redirFile = start;
      break;
    }
    
    else
      start++;

  }
}

int main(){
  while(1){
    //每次执行完一次重定向都需要更新一下值
    redirFile = NULL;
    redirType = NONE_REDIR;
    errno = 0;

    //输出提示符
    printf("用户名@主机名 当前路径# ");
    fflush(stdout);
  
    //从stdin获取输入,输入结束要有'\n'
    char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
    assert(s != NULL);
  
    (void)s;
    //清除最后一个'\n'
    lineCommand[strlen(lineCommand) - 1] = 0;
  
    commandCheck(lineCommand);


    //字符串切割,获取输入的指令
    myargv[0] = strtok(lineCommand, " ");
    int i = 1;
  
    //将颜色选项放入ls命令中
    if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
      myargv[i++] = (char*)"--color=auto";
  
    //没有子串的话,strtok返回NULL
    //依次获取指令后面的选项
    while(myargv[i++] = strtok(NULL, " "))
      ;
  
    //cd命令不会创建子进程,就让shell自己执行对应命令,执行系统接口
    if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0){
      if(myargv[1] != NULL) 
        //更改子进程当前工作目录
        chdir(myargv[1]);
      continue;
    }

    //echo $? 会打印出退出状态和终止信息
    //其余的会将字符串打印出
    if(myargv[0] != NULL && strcmp(myargv[0], "echo") == 0){
      if(strcmp(myargv[1], "$?") == 0)
        printf("%d, %d\n", lastCode, lastSig);

      else
        printf("%s\n", myargv[1]);

      continue;
    }
  
    //执行命令
    //创建子进程让子进程执行替换
    pid_t id = fork();
    assert(id != -1);
  
    if(id == 0){
      switch(redirType){
        case NONE_REDIR:
          break;
        //如果是重定向输入那就直接打开文件重定向给0文件描述符
        case INPUT_REDIR:
          {
            int fd = open(redirFile, O_RDONLY);
            if(fd < 0){
              perror("open");
              exit(errno);
            }
            dup2(fd, 0);
          }
          break;
        case OUTPUT_REDIR:
          {
            umask(0);
            int fd = open(redirFile, O_WRONLY | O_CREAT, 0666);
            if(fd < 0){
              perror("open");
              exit(errno);
            }
            dup2(fd, 1);
          }
          break;
        case APPEND_REDIR:
          {
            umask(0);
            int fd = open(redirFile, O_WRONLY | O_CREAT | O_APPEND, 0666);
            if(fd < 0){
              perror("open");
              exit(errno);
            }
            dup2(fd, 1);
          }
          break;
        default:
          printf("????\n");
          break;
      }


      execvp(myargv[0], myargv);
      exit(1);
    }
  
    //父进程等待接受子进程的信息
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    assert(ret > 0);
    (void) ret;
  
    lastCode = ((status>>8) & 0xFF);
    lastSig = (status & 0x7F);
  
  }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】---文件基础I/O 的相关文章

  • xsel -o 对于 OS X 等效项

    是否有一个等效的解决方案可以在 OS X 中抓取选定的文本 就像适用于 Linux 的 xsel o 一样 只需要当前的选择 这样我就可以在 shell 脚本中使用文本 干杯 埃里克 你也许可以安装xsel在 MacOS 上 更新 根据 A
  • 在 Mac OS X 上构建 Linux 内核

    我正在做一个修改Linux内核的项目 我有一台桌面 Linux 机器 在上面构建内核没有问题 不过 我要去旅行 我想在途中工作 我只有一台 MacBook 当我尝试构建 Linux 内核时 它抱怨说elf h was not found 我
  • 拆分字符串以仅获取前 5 个字符

    我想去那个地点 var log src ap kernelmodule 10 001 100 但看起来我的代码必须处理 ap kernelmodule 10 002 100 ap kernelmodule 10 003 101 等 我想使用
  • bluetoothctl 到 hcitool 等效命令

    在 Linux 中 我曾经使用 hidd connect mmac 来连接 BT 设备 但自 Bluez5 以来 这种情况已经消失了 我可以使用 bluetoothctl 手动建立连接 但我需要从我的应用程序使用这些命令 并且使用 blue
  • awk 子串单个字符

    这是columns txt aaa bbb 3 ccc ddd 2 eee fff 1 3 3 g 3 hhh i jjj 3 kkk ll 3 mm nn oo 3 我可以找到第二列以 b 开头的行 awk if substr 2 1 1
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • 如何使用 bash 锁定文件

    我有一个任务从远程服务器同步目录 rsync av email protected cdn cgi l email protection srv data srv data 为了使其定期运行并避免脚本 reEnter 问题 我使用 rsyn
  • 仅打印“docker-container ls -la”输出中的“Names”列

    发出时docker container ls la命令 输出如下所示 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a67f0c2b1769 busybox tail f dev
  • nginx 上的多个网站和可用网站

    通过 nginx 的基本安装 您的sites available文件夹只有一个文件 default 怎么样sites available文件夹的工作原理以及如何使用它来托管多个 单独的 网站 只是为了添加另一种方法 您可以为您托管的每个虚拟
  • 为什么内核需要虚拟寻址?

    在Linux中 每个进程都有其虚拟地址空间 例如 32位系统为4GB 其中3GB为进程保留 1GB为内核保留 这种虚拟寻址机制有助于隔离每个进程的地址空间 对于流程来说这是可以理解的 因为有很多流程 但既然我们只有 1 个内核 那么为什么我
  • 我可以从命令行打印 html 文件(带有图像、css)吗?

    我想从脚本中打印带有图像的样式化 html 页面 谁能建议一个开源解决方案 我使用的是 Linux Ubuntu 8 04 但也对其他操作系统的解决方案感兴趣 你可以给html2ps http user it uu se jan html2
  • Linux中的定时器类

    我需要一个计时器来以相对较低的分辨率执行回调 在 Linux 中实现此类 C 计时器类的最佳方法是什么 有我可以使用的库吗 如果您在框架 Glib Qt Wx 内编写 那么您已经拥有一个具有定时回调功能的事件循环 我认为情况并非如此 如果您
  • sendfile64 只复制约2GB

    我需要使用 sendfile64 复制大约 16GB 的文件 到目前为止我所取得的成就是 include
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 如何在Linux内核源代码中打印IP地址或MAC地址

    我必须通过修改 Linux 内核源代码来稍微改变 TCP 拥塞控制算法 但为了检查结果是否正确 我需要记录 MAC 或 IP 地址信息 我使用 PRINTK 函数来打印内核消息 但我感觉很难打印出主机的MAC IP地址 printk pM
  • Linux:在文件保存时触发 Shell 命令

    我想在修改文件时自动触发 shell 命令 我认为这可以通过注册 inotify 挂钩并调用来在代码中完成system 但是是否有更高级别的 bash 命令可以完成此任务 尝试 inotify 工具 我在复制链接时遇到问题 抱歉 但 Git
  • 使用 sh 运行 bash 脚本

    我有 bash 脚本 它需要 bash 另一个人尝试运行它 sh script name sh 它失败了 因为 sh 是他的发行版中 dash 的符号链接 ls la bin sh lrwxrwxrwx 1 root root 4 Aug
  • ubuntu:升级软件(cmake)-版本消歧(本地编译)[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我的机器上安装了 cmake 2 8 0 来自 ubuntu 软件包 二进制文件放置在 usr bin cmake 中 我需要将 cmake 版本至少

随机推荐

  • umi3 antDesignPro布局layout配置快速搭建项目

    umi3 快速搭建项目 1 环境准备 2 创建项目 3 修改配置 应用antd layout布局 1 安装antd layout插件 2 修改 umirc 文件配置路由 3 根据配置文件中component新建路由对应的组件 1 环境准备
  • c语言中weak的作用

    转载至 https blog csdn net q2519008 article details 82774774 在u boot源码中看到 weak关键字 在移植过程中遇到了问题 用例 weak在不同的环境中用法不同 在stm32源码中也
  • 合宙Air105

    基础资料 基于Air105开发板 Air105 LuatOS 文档 上手 开发上手 LuatOS 文档 探讨重点 官方SFUD库操作 外置flash demo相关内容的学习及探讨 扩展 合宙Air103 SDIO 扩展 LuatOS SOC
  • 服务器UDIMM, LRDIMM,RDIMM三种内存的区别

    服务器UDIMM LRDIMM RDIMM三种内存的区别 UDIMM RDIMM LRDIMM 区别与应用 随着应用程序的不断增长 内存被迫承担着更大压力 目前不管是服务器租用还是PC领域 DDR4内存技术依旧是主流 由于DDR4采用并行传
  • keil stm32f407工程环境搭建

    一 库函数 1 安装https www keil com dd2 Pack 2Feula container 直接双击Keil STM32F4xx DFP 1 0 8 pack 二 创建工程 为当前工程添加相应的库函数 点击确定 函数添加成
  • 可能影响经济体安全的技术类别

    这份新的技术出口管理新提案内容相对简洁 清晰罗列了可能会影响强大国家安全或者经济体的14类新兴和基础技术 1 生物技术 例如 1 纳米生物学 2 合成生物学 3 基因组和基因工程 4 神经科学 2 人工智能 AI 和机器学习技术 例如 1
  • mac 下 jdgui invalid input fileloader

    在一次反编译中 前面几个步骤都是正确的 将classes dex成功转为classes dex2jar jar文件 在即将把classes dex2jar jar文件在jd gui中打开的时候 出现了jdgui invalid input
  • 判断ListView的第一个/最后一个item是否完全显示

    判断最后一项 亲测可用 当然网上还有很多其他的方法 不同场景的方式可能不一样 Override public void onScroll AbsListView view int firstVisibleItem int visibleIt
  • Android shape渐变色用代码怎么写?

    前言 shape在实际开发中非常常用 一般我们会在xml中使用 但涉及到颜色动态变更时 我们需要在代码中动态创建 xml中的shape 实际上被创建出来后它是一个Drawable 点开Drawable的子类一看 我们很容易就发现一些可疑的实
  • 【Android】DataBinding 最全使用解析

    DataBinding 最全使用解析 一 DataBinding 概述 二 基本用法 2 1 使用入门 2 2 布局和绑定表达式 2 3 事件绑定 2 4 单向绑定 2 5 双向绑定 三 高级用法 BindingAdapter 一 Data
  • Linux节点释放,关于linux:如何释放Inode的使用量?

    我有一个磁盘驱动器 其索引节点使用率为100 使用df i命令 但是 在实质上删除文件后 使用率仍为100 那么正确的方法是什么 磁盘空间使用较少的磁盘驱动器如何可能具有 Inode的使用率比磁盘空间使用率更高的磁盘驱动器高 如果我压缩大量
  • 前端面试大全(jQuery篇——含移动端常见问题)

    目录 面试系列 内容介绍 1 JQuery的源码看过吗 能不能简单概况一下它的实现原理 2 jQuery fn的init方法返回的this指的是什么对象 为什么要返回this 3 jquery中如何将数组转化为json字符串 然后再转化回来
  • harbor的https访问方式及自定义证书

    一 基本安装 docker docker compose 二 https访问harbor需要自定义证书 1 首先创建存放证书的目录 到对应目录证书的位置 root host1 harbor mkdir opt cert cd opt cer
  • matlab fminbnd 寻找区域极值

    fminbnd 进行有约束的一元函数最小值求解 它的求解命令是 X FMINBND FUN x1 x2 FUN 是目标函数 可以为表达式字符串或MATLAB自定义函数的函数柄 要求解在约束 x1 lt X lt x2下的最优解X 还有其他一
  • 干货丨什么是虚拟化技术?虚拟化常见架构

    在计算机中 虚拟化 英语 Virtualization 是一种资源管理技术 是将计算机的各种实体资源 如服务器 网络 内存及存储等 予以抽象 转换后呈现出来 打破实体结构间的不可切割的障碍 使用户可以比原本的组态更好的方式来应用这些资源 这
  • STM32固件库(标准外设库)入门学习 第六章TIM定时器(二)

    STM32固件库 标准外设库 入门学习 第六章TIM定时器 二 文章目录 STM32固件库 标准外设库 入门学习 第六章TIM定时器 二 前言 一 定时中断代码 1接线图 2 程序编写 2 1 第一步开启RCC时钟 2 2 第二步选择时基单
  • 大数据应用期末总评

    本作业来自于 https edu cnblogs com campus gzcc GZCC 16SE1 homework 3363 一 将爬虫大作业产生的csv文件上传到HDFS 将爬虫大作业中爬取到的数据文件csv导入到 usr loca
  • 前端API接口的调用

    一 开启API接口 首先我们把模型部署在自己的服务器上之后开启模型的接口 linux环境下 进入模型文件 输入命令行 bash webui sh listen api 实现api接口的开启 我们获得一个api接口的地址 二 API接口调用并
  • Springboot的部分依赖及作用

    SpringBoot2使用Undertow来提高应用性能 spring boot starter undertow
  • 【Linux】---文件基础I/O

    文章目录 回顾C语言文件操作接口 文件相关的系统调用接口 打开和关闭文件 文件的打开方式 文件描述符 文件描述符的分配规则 write read 重定向 dup2 mysell 回顾C语言文件操作接口 在C语言中对于文件的操作有着几个常用的