Linux:理解文件重定向

文章目录

  • 文件内核对象
  • fd的分配问题
  • 重定向的现象
    • dup2
  • 重定向的使用
    • 标准输出和标准错误

前面对于文件有了基本的认知,那么基于前面的认知,本篇总结的是文件重定向的含义极其本质

文件内核对象

首先理解一下file内核对象是什么,回顾一下下面这张图

在这里插入图片描述
站在用户的角度,对于文件的操作有这些诸如readwrite这些系统调用,也有这些系统调用进行了一定的封装后诞生的C语言库函数,这些系统调用的内部会在内存中进行一系列操作

首先,在进程运行起来后首先会创建出进程的PCB,这是一定的,其次,进程的PCB中有这么一块专门的区域名字叫作fd_array[],它的本质是一个指针数组,它会指向一个一个结构体,而每一个结构体中存储的是关于这个文件的信息,这个结构体叫做struct file,而前面所说的文件描述符fd,本质上就是这个指针数组的下标,操作系统默认会为用户打开三个文件,分别是标准输入,标准输出,标准错误,关于这三个文件的用处就是本文要重点解析的内容,也是理解文件重定向所必须要理解的内容核心

而这个file结构体就是用来管理这些系统调用中需要的一些操作,以读写函数为例,假设现在用户使用了读的系统调用,那么落实到内存中,进程的PCB就会根据在系统调用中这个fd索引,找到对应的文件结构体,进而就能找到这个文件的信息,而接下来关于这个文件的一系列操作,都要借助文件缓冲区来帮助,还是以读这个系统调用为例,当现在需要进行读取数据的时候,由冯诺依曼体系可以知道,CPU不会和外设打交道,也就是说在磁盘中的文件信息是不能直接和CPU进行交互的,因此要首先加载到内存中,因此就会加载到文件缓冲区中,而此时进程就会从文件缓冲区中读取所需要的信息,进而给用户或者是其他函数一个反馈

写数据也是一个道理,但是不管是写数据还是读数据,由于冯诺依曼体系的原因,是一定要先加载到内存中去的,无论读写,都需要加载到文件缓冲区

因此可以得到一个观点:在应用层对于数据的读写,本质上是将内核缓冲区的数据进行来回拷贝

由此结束了关于file内核对象的理解,下面开始进行下一个模块的理解

fd的分配问题

来看示例代码

void testfd0()
{
    char buffer[1024];
    ssize_t s = read(0, buffer, 1024);
    buffer[s - 1] = 0;
    printf("%s\n", buffer);
}

调用了系统调用接口,从标准输入流中读取了信息,再进行输出

从中可以得出一个结论:进程默认打开了012三个文件,用户可以直接使用012进行数据的访问

// fd的分配原则
void testfd1()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    printf("fd -> %d\n", fd);
    close(fd);
}

输出:

[test@VM-16-11-centos File]$ ./myfile 
fd -> 3

这是符合预期的,因为系统会默认打开三个文件,分别占用0,1,2这三个位置,那如果把这三个文件关掉一个呢?

void testfd2()
{
    close(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    printf("fd -> %d\n", fd);
    close(fd);
}

输出:

[test@VM-16-11-centos File]$ ./myfile 
fd -> 0

由此可以推测出,文件描述符的分配规则是:寻找最小的,没有被使用的数据的位置,分配给指定的打开文件

重定向的现象

关于什么是重定向,用下面的demo来做一个示范

// 重定向
void redir1()
{
    close(1);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open fail\n");
        exit(1);
    }
    printf("fd-> %d\n", fd);
    printf("stdout->fd: %d\n", stdout->_fileno);
    fflush(stdout);
    close(fd);
}

运行结果是,在屏幕上没有任何信息,但在log.txt中居然出现了输出信息

在这里插入图片描述
那么这是为什么呢?从代码的目的来看,代码一开始就关闭了标准输出流文件,其次又打开了一个新的fd文件,那么根据前面fd
的分配规则,这个新打开的fd的下标就是1,因为标准输出的下标是1,这是可以确定的

其次,代码的下一步是打印了一些信息,但是这些信息都被打印到了文件中,但是理想状态下,信息应该要打印到显示器上,这说明了此时printf这个函数的打印发生了一些变化,原本它的打印目标是显示器,现在却打印到了文件中,而从输出的第二条信息也能看出,现在要打印一下stdout的文件描述符是多少,此时打印出来的结果是1,而我们新打开的文件的下标也是1,那么可不可以说,这个新打开的文件描述符取代了原来的stdout呢?

答案确实是这样,与其说stdout在系统中只认文件描述符,不如说它只认1,在打印的时候它只会找1,哪里有1它就打印到哪里,在正常的逻辑中,stdout对应的文件描述符是1,而1这个文件描述符会在进程启动的时候就自动打开标准输出文件,因此在进程执行到printf这样的函数的时候,就会把信息打印到显示器上,这样就能让我们看到显示器上的内容,这是符合预期的

但是上面的示例代码中,却有了一个偷梁换柱感觉的操作,把原本文件描述符为1的文件换成了log.txt,此时再打印的时候,进程只会机械性的去寻找文件描述符为1的文件,因此就忽略了这个文件本身其实已经不是它了,而这恰恰也证明了在之前就一直输出的一个观点:Linux下一切皆文件,不管是显示器还是键盘还是网卡等等外部设备,操作系统都有自己的方法能把它变成内存中的一部分,这个方法前面也有提及,就是VFS技术

用下面的这张图来说明一下刚才上面代码的一系列操作原理:

在这里插入图片描述
重定向的本质,就是修改特征文件fd的下标内容

上层的fd不变,变化的是底层fd指向的内容,也就是所谓文件描述符级别的数组内容的拷贝

那这样的写法还是太奇怪了,每次都要把一个文件关闭再打开一个新的文件,作为系统理应给操作者提供这样替换文件描述符的系统调用,事实上也确实提供了这样的系统调用

dup2

DUP(2)                                                      Linux Programmer's Manual                                                     DUP(2)

NAME
       dup, dup2, dup3 - duplicate a file descriptor

SYNOPSIS
       #include <unistd.h>

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

       int dup3(int oldfd, int newfd, int flags);

DESCRIPTION
       These system calls create a copy of the file descriptor oldfd.

       dup() uses the lowest-numbered unused descriptor for the new descriptor.

       dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:

       *  If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.

       *  If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

       After  a  successful  return from one of these system calls, the old and new file descriptors may be used interchangeably.  They refer to
       the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset  is  modi‐
       fied by using lseek(2) on one of the descriptors, the offset is also changed for the other.

       The  two  descriptors do not share file descriptor flags (the close-on-exec flag).  The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for
       the duplicate descriptor is off.

简单来说,就是用oldfd去替换newfd,保留下来的是oldfd,那么上面的代码就可以被改良成这样:

void redir2()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if(fd < 0)
    {
        perror("open fail\n");
        exit(1);
    }
    dup2(fd, 1);
    printf("fd-> %d\n", fd);
    printf("stdout->fd: %d\n", stdout->_fileno);
    fflush(stdout);
    close(fd);
}

显然这是可行的

重定向的使用

重定向其实并不陌生,在之前的学习中已经用过重定向,只是那是还没有建立起来一个基础的概念,先看下面的指令演示

[test@VM-16-11-centos File]$ echo "hello linux" > log.txt 
[test@VM-16-11-centos File]$ echo "hello linux"
hello linux

这段指令的含义就是,把hello linux重定向到log.txt中,其实这样的操作符还有下面的这几种,一一进行介绍

>

这个操作符表示的是输出重定向,意思就是把内容输出到某个文件中,有些类似于以w的方式打开一个文件并进行写入

>>

这个操作符表示的是追加重定向,意思就是把内容追加输出到某个文件中,有些类似于append的方式进行写入

<

这个操作符表示的是输入重定向,表示把原来的内容输入输入到某个文件中,相当于是替换了标准输入流的文件

标准输出和标准错误

前面的知识已经足以理解为什么要有标准输入和标准输出,但是还有一个问题有待解决,标准错误的意义是什么呢?难道标准输出的信息还不够吗?

答案是确实不够,在对于大型项目的时候,会有很多的输出信息,这些输出信息有些是正常信息,有些是异常信息,而对于开发者来说他们需要的是错误信息,因此对于如何获取错误信息就显得至关重要,于是标准错误流信息就诞生了,对于正常来说可能没有太多的感觉,但是实际上,没有感觉的原因是因为标准错误和标准输出的文件对象都是显示器,而实际上这是可以被替换的,基于这个原理可以做出下面的测试代码

void teststderr()
{
    printf("this is normal message\n");
    perror("this is error message\n");
}
[test@VM-16-11-centos File]$ make
gcc -o myfile myfile.c -std=c99
[test@VM-16-11-centos File]$ ./myfile 1>out.txt 2>error.txt

利用上面的原理可以写出这样的测试代码,把正确信息存储到一个文件中,把错误信息存储到另外一个文件中,这样就能知道哪里是错误哪里是正确了

关于其他重定向

  1. 1>&2意思是把标准输出重定向到标准错误
  2. 2>&1意思是把标准错误输出重定向到标准输出

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

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

相关文章

python-nmap库使用教程(Nmap网络扫描器的Python接口)(功能:主机发现、端口扫描、操作系统识别等)

文章目录 Python-nmap库使用教程前置条件引入python-nmap创建Nmap扫描实例执行简单的主机发现&#xff08;nmap -sn&#xff09;示例&#xff0c;我有一台主机配置为不响应 ICMP 请求&#xff0c;但使用nmap -sn&#xff0c;仍然能够探测到设备&#xff1a; 端口扫描扫描特定端…

从setText处理来学习绘制流程

Android中TextView调用setText是会进行text文字的更新&#xff0c;是一个比较简单的画面变化&#xff0c;这可以作为一个出发点来查看绘制处理流程。这里来问问chatGPT&#xff0c;来查看大致流程 请讲讲Android中textView的setText处理流程 ChatGPT Poe 当你调用 textView.s…

二分算法(整数二分、浮点数二分)

文章目录 二分一、整数二分&#xff08;一&#xff09;整数二分思路&#xff08;二&#xff09;整数二分算法模板1.左查找&#xff08;寻找左侧边界&#xff09;2.右查找&#xff08;寻找右侧边界&#xff09;3.总模板 &#xff08;三&#xff09;题目&#xff1a;数的范围 二、…

【linux网络】补充网关服务器搭建,综合应用SNAT、DNAT转换,dhcp分配、dns分离解析,nfs网络共享以及ssh免密登录

目录 linux网络的综合应用 1&#xff09;网关服务器&#xff1a;ens35&#xff1a;12.0.0.254/24&#xff0c;ens33&#xff1a;192.168.100.254/24&#xff1b;Server1&#xff1a;192.168.100.101/24&#xff1b;PC1和server2&#xff1a;自动获取IP&#xff1b;交换机无需…

spring框架的事务传播级别经典篇

一 spring事务传播级别 1.1 总结概述 方法A:外围方法&#xff0c;方法B&#xff1a;内部方法&#xff0c;在A中调用B 1.事务级别PROPAGATION_REQUIRED&#xff1a; 如果A为PROPAGATION_REQUIRED&#xff1a;B 不管有没有设置事务级别&#xff0c;都会加入到A的事务级别中。如…

低代码究竟有何特别之处?为什么很多企业倾向于用低代码开发软件?

目录 一、低代码是什么 二、低代码有哪些核心能力&#xff1f; 三、低代码能做哪些事情&#xff1f; 1、软件开发快效率高 2、满足企业的多样化需求 3、轻松与异构系统集成 4、软件维护成本低 5、为企业实现降本增效 四、结语 低代码平台正高速发展中&#xff0c;越来越多的企业…

phpoffice在tp框架中如何实现导入导出功能

安装 phpoffice/phpspreadsheet 库 composer require phpoffice/phpspreadsheet 导入功能 创建一个用于上传文件的视图&#xff0c;可以使用元素来实现文件上传。 <!-- application/view/your/import.html --><form action"{:url(your/import)}" method&q…

智慧博物馆视频监控系统设计,可视化AI智能分析技术助力博物馆多维度监管

一、背景与需求 博物馆视频智能监控系统是智慧博物馆建设的重要组成部分&#xff0c;传统的博物馆视频监控系统以模拟系统架构为主&#xff0c;存在监管效率低、各个系统独立运作形成数据孤岛、以“事后补救”为主要监管手段等管理弊病&#xff0c;无法满足互联网高速发展背景…

学习笔记:Pytorch 搭建自己的Faster-RCNN目标检测平台

B站学习视频 up主的csdn博客 1、什么是Faster R-CNN 2、pytorch-gpu环境配置&#xff08;跳过&#xff09; 3、Faster R-CNN整体结构介绍 Faster-RCNN可以采用多种的主干特征提取网络&#xff0c;常用的有VGG&#xff0c;Resnet&#xff0c;Xception等等。 Faster-RCNN对输入…

Re8 Generative Modeling by Estimating Gradients of the Data Distribution

宋扬博士的作品&#xff0c;和DDPM同属扩散模型开创工作&#xff0c;但二者的技术路线不同 Introduction 当前生成模型主要分成两类 基于似然模型 通过近似最大似然直接学习分布的概率密度&#xff0c;如VAE 隐式生成模型 概率分布由其抽样过程的模型隐式表示&#xff0c…

Verilog 入门(三)(表达式)

文章目录 操作数操作符算术操作符关系操作符相等关系操作符逻辑操作符按位操作符条件操作符 操作数 操作数可以是以下类型中的一种&#xff1a; 常数参数线网寄存器位选择部分选择存储器单元函数调用 操作符 Verilog HDL中的操作符可以分为下述类型&#xff1a; 算术操作符…

WPF窗口样式的比较

WPF窗口样式的比较 1.WPF默认Window窗口 带有图标 标题栏 最小最大化推出按钮 <Window x:Class"GlowWindowDemo.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006…

在Spring Boot中使用JavaMailSender发送邮件

用了这么久的Spring Boot&#xff0c;我们对Spring Boot的了解应该也逐步进入正轨了&#xff0c;这篇文章讲的案例也在我们的实际开发中算是比较实用的了&#xff0c;毕竟我们完成注册功能和对用户群发消息&#xff0c;都可以采用到邮箱发送功能&#xff0c;往下看&#xff0c;…

焕发图片生机,批量升级gif图片像素,打造高质量图片盛宴!

你是否曾经遇到过需要提高gif图片质量&#xff0c;但手动处理每一张图片又非常耗时且繁琐的情况&#xff1f;如果你觉得处理大量图片会让你感到压力&#xff0c;那么你一定需要我们的批量提高像素工具&#xff01; 第一步&#xff0c;首先我们要进入首助剪辑高手主页面&#x…

ELFK集群部署(Filebeat+ELK) 本地收集nginx日志 远程收集多个日志

filebeat是一款轻量级的日志收集工具&#xff0c;可以在非JAVA环境下运行。 因此&#xff0c;filebeat常被用在非JAVAf的服务器上用于替代Logstash&#xff0c;收集日志信息。 实际上&#xff0c;Filebeat几乎可以起到与Logstash相同的作用&#xff0c; 可以将数据转发到Logst…

正式版PS 2024 25新增功能 刚刚发布的虎标正式版

Adobe Photoshop 2024是一款业界领先的图像编辑软件&#xff0c;被广泛应用于设计、摄影、插图等领域。以下是这款软件的一些主要功能和特点&#xff1a; 丰富的工具和功能。Adobe Photoshop 2024提供了丰富的工具和功能&#xff0c;可以帮助用户对图像进行编辑、修饰和优化。…

虚拟数据生成_以Python为工具

生成虚拟数据_以Python为工具 生成虚拟数据技术在现实生活中具有多个重要的应用领域。它为数据隐私保护、机器学习算法开发、数据处理和可视化等方面提供了实用且有价值的解决方案。尤其是能满足定制化需求的虚拟数据&#xff0c;在预期的方向上让数据定向随机。 &#x1f339…

编程之外,生活的美好航程

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

volatile-之小总结

凭什么我们Java写了一个volatile关键字&#xff0c;系统底层加入内存屏障&#xff1f;两者的关系如何勾搭&#xff1f; 内存屏障是什么&#xff1f; 是一种屏障指令&#xff0c;它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约 束。也称为内存栅栏或栅…

概念理论类-k8s :架构篇

转载&#xff1a;新手通俗易懂 k8s &#xff1a;架构篇 Kubernetes&#xff0c;读音是[kubə’netis]&#xff0c;翻译成中文就是“库伯奈踢死”。当然了&#xff0c;也可以直接读它的简称&#xff1a;k8s。为什么把Kubernetes读作k8s&#xff0c;因为Kubernetes中间有8个字母…