【Linux系统编程】第二十七弹---文件描述符与重定向:fd奥秘、dup2应用与Shell重定向实战

news2024/10/4 11:48:08

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、文件描述符fd

1.1、0 & 1 & 2 

1.2、文件描述符的分配规则 

2、重定向

3、使用 dup2 系统调用

3.1、> 输出重定向

3.2、>> 追加重定向

3.3、< 输入重定向

3.4、shell模拟实现> >> <

4、缓冲区 


1、文件描述符fd

  • 通过对open函数的学习,我们知道了文件描述符就是一个小整数

查看open的返回值fd。

会用到的头文件

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

代码演示 

int main()
{
  // 查看open返回值是什么
  int fda = open("loga.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  printf("fda : %d\n",fda);
  int fdb = open("logb.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  printf("fdb : %d\n",fdb);
  int fdc = open("logc.txt",O_WRONLY | O_CREAT | O_TRUNC,066);
  printf("fdc : %d\n",fdc);
  int fdd = open("logd.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  printf("fdd : %d\n",fdd);
  return 0;
}

运行结果 

结果是从3开始,且是依次递增的。

1.1、0 & 1 & 2 

fd为什么从3开始呢?0 1 2分别代表什么呢?

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2,根据文件描述符分配规则(后序一个标题详细讲解),找到当前没有被使用的最小的一个下标,作为新的文件描述符
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器。

补充(使用系统调用读文件):

#include <unistd.h>

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

尝试从文件描述符 fd 读取最多 count 个字节到从 buf 开始的缓冲区。

验证一

int main()
{
  char buf[1024];
  // 从键盘读取sizeof(buf)个字节到buf中
  ssize_t s = read(0, buf, sizeof(buf));
  if(s > 0)
  {
    buf[s] = 0;// 设置结尾\0
    // 将buf的strlen(buf)长度写到显示器中
    write(1, buf, strlen(buf));
    write(2, buf, strlen(buf));
  }
  return 0;
}

运行结果 

内核结构 

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

进一步验证

fd在C语言层面其实是FILE结构体中的一个 _fileno 成员,我们可以打印这个成员的结果来验证0,1,2。

代码演示  

int main()
{
  //0 1 2 默认打开 
  printf("stdin->fd: %d\n",stdin->_fileno);
  printf("stdout->fd: %d\n",stdout->_fileno);
  printf("stderr->fd: %d\n",stderr->_fileno);
  // 普通文件创建的
  FILE* fp = fopen("log.txt","w");
  if(fp == NULL) return 1;
  printf("fd: %d\n",fp->_fileno);

  FILE* fp1 = fopen("log1.txt","w");
  if(fp == NULL) return 1;
  printf("fd: %d\n",fp1->_fileno);
  
  FILE* fp2 = fopen("log2.txt","w");
  if(fp == NULL) return 1;
  printf("fd: %d\n",fp2->_fileno);
  return 0;
}

运行结果 

1.2、文件描述符的分配规则 

关闭0或者2

int main()
{
  close(2);
  //close(0);
  int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  if(fd < 0) 
  {
    perror("open");
    return 1;
  }
  printf("fd: %d\n",fd);
  return 0;
}

运行结果 

从关闭0号和2号文件描述符我们可以看到,关闭几号创建新文件的fd就是几号。 

文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

2、重定向

代码演示 

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

运行结果 

此时,我们发现,本来应该输出到显示器上的内容输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <。


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

上层用的 fd 不变,在内核中更改 fd 对应的 struct file* 地址。

3、使用 dup2 系统调用
 

#include <unistd.h>

int dup2(int oldfd, int newfd);

文件描述符下标内容的拷贝,将oldfd拷贝给newfd。

3.1、> 输出重定向

将新文件描述符下标内容拷贝到显示器上。

代码演示  

int main()
{
  int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }
  dup2(fd,1);
  printf("hello linux\n");
  
  return 0;
}

运行结果 

3.2、>> 追加重定向

将新文件描述符下标内容追加拷贝到显示器上。

代码演示  

int main()
{
  int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }
  dup2(fd,1);
  printf("hello linux\n");
  fprintf(stdout,"hello linux\n"); 
  return 0;
}

运行结果 

3.3、< 输入重定向

 将新文件描述符下标内容拷贝到键盘上。

代码演示  

int main()
{
    int fd=open("log.txt",O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        return 1;
    }
    //输入重定向
    dup2(fd,0);
    char outbuffer[64];
    while(1)
    {
        // 获取fd = 0中数据,即文件
        if(fgets(outbuffer,sizeof(outbuffer),stdin) == NULL) break;
        printf("<%s",outbuffer);
    }
    return 0;
}

运行结果 

3.4、shell模拟实现> >> <

头文件、宏、全局变量

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
// 找最后一个/ ,宏是替换可以不用传二级指针,do while 不加分号,为了后面加分号
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{\
    while(1){\
        if(isspace(cmd[pos]))\
            pos++;\
        else break;\
    }\
}while(0)

char* gArgv[NUM];
int lastcode = 0;
char cwd[SIZE*2];

// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir   1
#define Out_Redir  2
#define App_Redir  3

int redir_type = None_Redir;
char *filename = NULL;

1.CheckRedir

在获取字符串之后检查是否有重定向符号,通过全局变量redir_type赋予不同的值,默认没有重定向符号。

代码演示  

void CheckRedir(char cmd[])
{
  // > >> <
  // "ls -a -l > myfile.txt"
  int pos = 0;
  int end = strlen(cmd);

  while(pos < end)
  {
    if(cmd[pos] == '>')
    {
      if(cmd[pos + 1] == '>')
      {
        cmd[pos++] = 0;
        pos++;
        redir_type = App_Redir;
        SkipSpace(cmd,pos);
        filename = cmd + pos;
      }
      else 
      {
        cmd[pos++] = 0;
        redir_type = Out_Redir;
        SkipSpace(cmd,pos);
        filename = cmd + pos;
      }
    }
    else if(cmd[pos] == '<')
    {
      cmd[pos++] = 0;
      redir_type = In_Redir;
      SkipSpace(cmd,pos);
      filename = cmd + pos;
    }
    else 
    {
      pos++;
    }
  }
}

2.测试CheckRedir

 打印出对应的变量值即可。

    printf("cmd: %s\n",usercommand);
    printf("redir: %d\n",redir_type);
    printf("filename: %s\n",filename);

运行结果 

4、缓冲区 

缓冲区是什么?

缓冲区是一段内存空间。

为什么要有缓冲区?

给上层提供高效的IO体验,间接提高整体的效率。

缓冲区的刷新策略

正常情况

        1、立即刷新。fflush(stdout)  int fsync(int fd); synchronize a file's in-core state with storage device。

        2、行刷新。显示器刷新,为了照顾用户的体验。

        3、全缓冲。缓冲区写满才刷新(普通文件)。

特殊情况

        1、进程退出,系统自动刷新

        2、强制刷新

缓冲器包括?

用户级缓冲区 内核级缓冲区

缓冲区的意义:

1、解耦

2、提高效率,提高用户使用的效率,提高刷新IO的效率

代码演示  

int main()
{
  printf("hello printf\n");
  fprintf(stdout,"hello fprintf\n");

  const char* msg = "hello write\n";
  write(1,msg,strlen(msg));
  return 0;
}

运行结果 

奇怪的代码 

int main()
{
  printf("hello printf\n");
  fprintf(stdout,"hello fprintf\n");

  const char* msg = "hello write\n";
  write(1,msg,strlen(msg));
  fork();
  return 0;
}

运行结果 

我们发现 printf 和 fprintf (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

  •  一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲

综上printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

如果有兴趣,可以看看FILE结构体:


typedef struct _IO_FILE FILE; 在/usr/include/stdio.h

在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2187947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《业务三板斧:定目标、抓过程、拿结果》读书笔记1

这个书是24年新书&#xff0c;来自阿里系的人的作品&#xff0c;还可以。今天先看前沿部分的精彩部分&#xff1a; 我们在服务企业的过程中&#xff0c;发现了一个常见的管理现象&#xff1a;管理者自 己承担了团队里重要的项目&#xff0c;把风险和压力都集中在自己身上。因 此…

(10)MATLAB莱斯(Rician)衰落信道仿真1

文章目录 前言一、莱斯分布随机变量二、仿真代码与结果1.仿真代码2.仿真结果画图 后续 前言 首先给出莱斯衰落信道模型&#xff0c;引入了莱斯因子K&#xff0c;并给出莱斯分布的概率密度函数公式。然后导出莱斯分布随机变量的仿真表示式&#xff0c;建立MATLAB仿真代码&#…

为什么晶振被称为芯片的心脏?(全网最准确解释)

晶振是电路中非常常见的一个元件&#xff0c;常常被人们称为芯片的心脏&#xff0c;确实如此&#xff0c;没有了晶振&#xff0c;可以说一般情况下芯片就无法工作。 为什么晶振被称为芯片的心脏&#xff1f;&#xff08;全网最准确解释&#xff09; STM32 软件触发ADC采集 STM…

OpenAI o1 与 GPT-4o:前沿AI全面比较下你更倾向哪一款

前言 就在前不久&#xff0c;OpenAI 发布了推理能力更强可达理科博士生水准的o1 模型&#xff0c;业界也表示这标志着人工智能发展的新里程碑&#xff0c;特别是在复杂问题解决和推理方面。 然而&#xff0c;该模型与其前身GPT-4o有很大不同&#xff0c;后者仍然广泛用于通用…

【JAVA开源】基于Vue和SpringBoot的宠物咖啡馆平台

本文项目编号 T 064 &#xff0c;文末自助获取源码 \color{red}{T064&#xff0c;文末自助获取源码} T064&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(终篇)

Deforum 中其他模块主要是服务于上传视频的&#xff0c;而视频转绘有其他效果更好的插件。 初始化 除了 图像初始化 模块&#xff0c;其他模块都用不上&#xff1a; 上一篇文章生成的视频&#xff0c;都用了图像初始化功能。 像我这样&#xff0c;拖入一张图片&#xff0c;D…

Java - LeetCode面试经典150题 - 矩阵 (四)

矩阵 36. 有效的数独 题目 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff0…

【黑马点评】2 商户查询缓存

【黑马点评】2 商户查询缓存 2 商户查询缓存2.1 添加商户缓存2.1.1 添加商户信息缓存 --修改ShopController中的queryShopById方法2.1.2 添加商户类别缓存&#xff08;作业&#xff09;--修改ShopController中的queryTypeList方法 2.2 缓存更新策略2.2.1 数据库缓存不一致解决方…

测试H7-TOOL的CANFD助手J1939批量数据传输连续运行30个小时稳定不丢包

根据客户需求做的一个不断电连续运行测试。 测试条件 1、H7-TOOL的CAN/CANFD助手控制一个节点设备 2、使用J1939协议 3、经典CAN方式&#xff0c;波特率250Kbps J1939测试命令&#xff0c;250ms一次发送接收测试 昨天下午三点到今晚9点半&#xff0c;共计30个小时不断电连续测…

C++ STL 初探:打开标准模板库的大门

文章目录 C STL 初探&#xff1a;打开标准模板库的大门前言第一章: 什么是STL&#xff1f;1.1 标准模板库简介1.2 STL的历史背景1.3 STL的组成 第二章: STL的版本与演进2.1 不同的STL版本2.2 STL的影响与重要性 第三章: 为什么学习 STL&#xff1f;3.1 从手动编写到标准化解决方…

MongoDB集群模式详解及应用实战

目录 本节课内容: 集群搭建 1.创建3个目录: 2.编辑配置文件 ​编辑 3.启动: 4.看看: 5.另外,两个如上1,2,3步骤操作 ,但是日志目录,端口什么的需要改一下即可。 6.准备联通性: 先连主节点: 搭建集群环境(看文档) 本节课内容: 集群搭建 1.创建3个目录…

两数相除(c语言)

1.//给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 //整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。 // 例如&#xff0c;8.345 将被截断为 8 &#xff0c;…

【黑马点评】1 项目导入以及短信功能

苍穹外卖结束之后&#xff0c;进一步学习Redis的使用。学习Redis的实战篇&#xff0c;黑马点评。 这是复现过程中的详细笔记。 【黑马点评】1 项目导入以及短信功能 1 项目导入以及短信登录1.1 导入黑马点评项目1.1.1 导入SQL1.1.2 有关当前模型1.1.3 导入后端项目1.1.4 导入前…

植物种类识别系统源码分享

植物种类识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

大数据分析案例-基于逻辑回归算法构建抑郁非抑郁推文识别模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

鼠标一动,桌面起飞?Kando 如何改变你的工作流

鼠标一动&#xff0c;桌面起飞&#xff1f;Kando 如何改变你的工作流 想要更快、更有趣地操作你的桌面&#xff1f;Kando&#xff0c;这款跨平台的 Pie Menu 工具&#xff0c;帮你实现这一切。本文将为你介绍 Kando 的功能特点、适用场景&#xff0c;以及如何快速上手。 什么是…

Stable Diffusion绘画 | 插件-Deforum:商业LOGO广告视频

第1步&#xff1a;在 Deforum 的「初始化」中上传需要展示的 LOGO图片&#xff1a; 第2步&#xff1a;在「运行」模块进行设置&#xff1a; 设置宽高&#xff1a;保持与初始化上传图片的像素一致设置迭代步数&#xff1a;步数越高&#xff0c;视频细节越好 其他参数保持默认&…

【树形DP】AT_dp_p Independent Set 题解

step 1 题意理解 有一棵有 N N N 个顶点的树&#xff0c;编号为 1 , 2 , … , N 1,2,…,N 1,2,…,N。 Taro 决定将每个顶点涂成白色或黑色。 在这里&#xff0c;不允许将相邻的两个顶点都涂成黑色。 找出可以涂色的方式数量&#xff0c;对 1 0 9 7 10^9 7 1097 取模。 …

工业物联网的安全与隐私保护—SunIOT

【大家好&#xff0c;我是唐Sun&#xff0c;唐Sun的唐&#xff0c;唐Sun的Sun。一站式数智工厂解决方案服务商】 在当今数字化的时代&#xff0c;工业物联网&#xff08;IIoT&#xff09;正以前所未有的速度改变着工业生产的模式和效率。然而&#xff0c;随着工业物联网的广泛…

如何对物理系统进行数学建模?

一、关键因素和步骤 对某一物理系统建模时&#xff0c;需要考虑以下关键因素和步骤&#xff1a; 系统定义与理解&#xff1a; 清晰定义系统边界和组成部分&#xff0c;理解系统的功能、结构和动力学特性。 分析系统输入、输出及其相互作用&#xff0c;确定系统的动态变量。 建…