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

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

💗系列专栏: 【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.mfbz.cn/a/886677.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字图像处理:空间域滤波

1.数字图像处理&#xff1a;空间域滤波 1.1 滤波器核&#xff08;相关核&#xff09;与卷积 图像上的邻域计算 线性空间滤波的原理 滤波器核&#xff08;相关核&#xff09;是如何得到的&#xff1f; 空间域的卷积 卷积&#xff1a;滤波器核与window中的对应值相乘后所有…

新品:新一代全双工音频对讲模块SA618F22-C1

SA618F22-C1是我司一款升级版的无线数字和音频二合一全双工传输模块&#xff0c;支持8路并发高音质通话。用户不仅可以通过串口实现数据的无线传输&#xff0c;还可以通过I2S数字音频或模拟音频接口来传输语音信号。该模块内置高速微控制器、回声消除电路、ESD静电防护、高性能…

vmware Workstation16设置批量虚拟机开机自启 vmAutoStart

文章目录 前言解压压缩包一、使用步骤1.获取虚拟机所在目录2.获取vmware所在目录3.测试启动4.开机自启 二、gitee总结 前言 vmware workstation16不支持虚拟机开机自启&#xff0c;通常的办法是写脚本&#xff0c;但是有个问题就是不能启动多台虚拟机&#xff0c;因为有时候会…

C# 字符与字符串

本课要点&#xff1a; 1、字符类Char的使用 2、字符串类String的使用 3、可变字符串****StringBuilder 4、常见错误 一 何时用到字符与字符串 问题&#xff1a; 输出C#**课考试最高分&#xff1a;**98.5 输出最高分学生姓名&#xff1a;张三 输出最高分学生性别&#x…

Pikichu-xss实验案例-通过xss获取cookie

原理图&#xff1a; pikachu提供了一个pkxss后台&#xff1b; 该后台可以把获得的cookie信息显示出来&#xff1b; 查看后端代码cookie.php&#xff1a;就是获取cookie信息&#xff0c;保存起来&#xff0c;然后重定向跳转到目标页面&#xff1b;修改最后从定向的ip&#xff0…

无人机控制和飞行、路径规划技术分析

无人机控制和飞行、路径规划技术是现代无人机技术的核心组成部分&#xff0c;它们共同决定了无人机的性能和应用范围。以下是对这些技术的详细分析&#xff1a; 一、无人机控制技术 无人机控制技术主要涉及飞行控制系统的设计、传感器数据的处理以及指令的发送与执行。飞行控…

Spring Session学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

太原网站制作打造企业网站的关键要素

太原网站制作&#xff1a;打造企业网站的关键要素 在数字化时代&#xff0c;企业网站成为了品牌形象和市场营销的重要一环。太原的企业在进行网站制作时&#xff0c;需要关注几个关键要素&#xff0c;以确保网站能够有效提升企业竞争力和用户体验。 **1. 目标明确** 在网站制…

Redis篇(数据类型)

目录 讲解一&#xff1a;简介 讲解二&#xff1a;常用 一、String类型 1. 简介 2. 常见命令 3. Key结构 4. 操作String 5. 实例 二、Hash类型 1. 简介 2. 常见命令 3. 3操作hash 4. 实例 三、List类型 1. 简介 2. 特征 3. 应用场景 4. 常见命令 5. 操作list …

Python办公自动化之Word

在现代办公环境中&#xff0c;自动化无疑是提升工作效率的关键。特别是处理文档的工作&#xff0c;很多人可能花费大量时间在重复性任务上。那么&#xff0c;有没有一种方法可以让我们用 Python 来自动化 Word 文档的操作呢&#xff1f;今天&#xff0c;我们来聊聊如何用 Pytho…

基于php的在线租房管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

光伏项目管理如何更高效化?

一、项目规划与启动阶段的优化 1、智能规划工具&#xff1a;光伏管理软件通常配备有智能项目规划模块&#xff0c;能够根据地理位置、气候条件、政策补贴等因素&#xff0c;自动计算最佳装机容量、预测发电量及收益&#xff0c;帮助项目团队快速制定合理的项目方案。这大大缩短…

golang grpc进阶

protobuf 官方文档 基本数据类型 .proto TypeNotesGo Typedoublefloat64floatfloat32int32使用变长编码&#xff0c;对于负值的效率很低&#xff0c;如果你的域有可能有负值&#xff0c;请使用sint64替代int32uint32使用变长编码uint32uint64使用变长编码uint64sint32使用变长…

Redis: 集群架构,优缺点和数据分区方式和算法

集群 集群指的就是一组计算机作为一个整体向用户提供一组网络资源 我就举一个简单的例子&#xff0c;比如百度&#xff0c;在北京和你在上海访问的百度是同一个服务器吗&#xff1f;答案肯定是不是的&#xff0c;每一个应用可以部署在不同的地方&#xff0c;但是我们提供的服务…

Navicat Premium 12 for Mac中文永久版

目录 一、安装二、修改rpk文件三、获取请求码四、获取jh码 Tip&#xff1a;由于一些jy词&#xff0c;一直不让我发布&#x1f644;&#xff0c;所以只能用拼音简写代替&#xff0c;是不是很无语&#xff0c;我也很无语&#xff0c;各位自行体会一下&#x1f612; 为了避免每次换…

1. 如何在服务器上租GPU跑实验 (以AutoDL为例) - 深度学习·科研实践·从0到1

目录 前言 1. 在AutoDL上注册账号 2. 在算力市场选择GPU 3. 创建实例 4. 控制台-容器实例界面&#xff08;核心&#xff09; 4.1 无卡模式&#xff08;常用&#xff09; 5. 帮助文档 前言 好记性不如烂笔头&#xff0c;本专栏将详细记录下本人学习深度学习工程实践&…

C(十一)scanf、getchar(第三弹)

问题引入&#xff1a;如何实现输入一串密码&#xff0c;如&#xff1a;“123 xxxx” &#xff0c;然后读取并确认&#xff0c;是 -- Y&#xff1b;否 -- N。 自然的&#xff0c;我们想到用scanf&#xff0c;但是在使用过程中你是否遇到跟我一样的困惑呢&#xff1f;如下&…

新闻推荐系统:Spring Boot的创新应用

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02 1. APM: Large Language Model Agent-based Asset Pricing Models Authors: Junyan Cheng, Peter Chin https://arxiv.org/abs/2409.17266 APM: 基于大型语言模型的代理资产定价模型&#xff08;LLM Agent-b…

C++20中头文件concepts的使用

<concepts>是C20中新增加的头文件&#xff0c;此头文件是concepts库的一部分&#xff0c;主要用于模板编程、泛型编程。包括 1.core language concepts&#xff1a; std::same_as&#xff1a;指定一种类型(type)与另一种类型是否相同。 std::derived_from&#xff1a;指定…