Linux:理解文件重定向

news2024/11/23 9:43:16

文章目录

  • 文件内核对象
  • 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意思是把标准错误输出重定向到标准输出

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

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

相关文章

菜鸟学习日记(Python)——基本数据类型

Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量&#xff0c;它没有类型&#xff0c;我们所说的"类型"是变量所指的内存中对象的类型。 等号&#xff08;&#xff09;用来…

Netty Review - 探索Channel和Pipeline的内部机制

文章目录 概念Channel Pipeline实现原理分析详解 Inbound事件和Outbound事件演示Code 概念 Netty中的Channel和Pipeline是其核心概念&#xff0c;它们在构建高性能网络应用程序时起着重要作用。 Channel&#xff1a; 在Netty中&#xff0c;Channel表示一个开放的连接&#xff…

【VScode】超详细图片讲解下载安装、环境配置、编译执行、调试

这里是目录 VScode是什么&#xff1f;VScode的下载和安装环境介绍安装中文插件 配置VScodeC/C开发环境下载和配置MinGW-w64 编译器套件下载&#xff1a;配置&#xff1a; 安装C/C插件在VScode上编写代码设置C/C编译选项创建执行任务编译执行如果想写其他代码在同一个文件夹在不…

【双向链表的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1. 双向链表的结构 2. 双向链表的实现 2.1 头文件 ——双向链表的创建及功能函数的定义 2.2 源文件 ——双向链表的功能函数的实现 2.3 源文件 ——双向链表功能的…

SS6811H38V/1.6A 两通道 H 桥驱动芯片

SS6811H 为舞台灯光和其它电机一体化应用 提供一种双通道集成电机驱动方案。SS6811H 有 两路 H 桥驱动&#xff0c;每个 H 桥可提供最大输出电流 1.6A (在 24V 和 Ta 25C 适当散热条件下)&#xff0c;可驱 动两个刷式直流电机&#xff0c;或者一个双极步进电机&#xff0c;或 …

ssm+java车辆售后维护系统 springboot汽车保养养护管理系统+jsp

以前汽车维修人员只是在汽车运输行业中从事后勤保障工作,随着我国经济的发展,汽车维修行业已经从原来的从属部门发展成了如今的功能齐备的独立企业。这种结构的转变,给私营汽修企业和个体汽修企业的发展带来了契机,私营企业和个体维修企业的加入也带动了整个汽修行业的整体水平…

[c++]—string类___深度学习string标准库成员函数与非成员函数

要相信别人能做出来自己一定可以做出来&#xff0c;只不过是时间没到而已 目录 &#x1f6a9;string类对象capacity操作 &#x1f4bb;reserve()保留 &#x1f4bb;resize() &#x1f6a9;string类对象元素访问操作 &#x1f4bb;operator[]和at() &#x1f4bb;operator…

Kubernetes 安全最佳实践:保护您的秘密

Kubernetes 是一个可用于微服务的开源容器编排平台。当我们想要部署容器化应用程序、自动化管理和扩展应用程序时&#xff0c;Kubernetes 非常有用。 在容器中运行单个微服务而不是在同一虚拟机中运行多个进程几乎总是更安全。每当我们在 Kubernetes 中启动任何 pod 时&#x…

「黄钊的AI日报·第二季」早鸟票,最后48小时~

每天5条AI内容点&#xff1a;不是新闻汇总&#xff0c;而是站在11年AI产品经理的视角&#xff0c;将原AI信息中的干货认知&#xff0c;提炼成我自己的文字、展示“what I see”。 做社群“AI产品经理大本营”6年以来&#xff0c;我都是在非常用心的输出AI干货&#xff1b;这份“…

vue3随机生成8位字母+数字

// 随机生成8位字母数字 export const autoPassword: any () > {// console.log("自动生成");//可获取的字符串const chars ABCDEFGHIJKLMNOPQRSTUVWSYZabcdefghijklmnopqrstuvwsyz0123456789;const list [];//通过随机获取八个字符串的索引下标for (let i 0;…

力扣11题 盛最多水的容器 双指针算法

11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明 你不能倾斜容器. 示…

【开源】基于Vue.js的超市账单管理系统的设计和实现

项目编号&#xff1a; S 032 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S032&#xff0c;文末获取源码。} 项目编号&#xff1a;S032&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统设计3.1 总体设计3.2 前端设计3…

操作系统进程与线程篇

目录 一、进程 1.1、进程状态 1.2、进程的控制结构 1.3、进程的控制 1.4、进程的上下文切换 二、线程 2.1.线程是什么 2.2、线程与进程的比较 2.3、线程的上下文切换 2.4、线程的实现 2.5、轻量级线程 三、进程间的通信方式 3.1、管道 3.2、消息队列 3.3、共享内…

Linux--系统结构与操作系统

文章目录 冯诺依曼体系结构为什么要有内存&#xff1f;场景一 操作系统何为管理&#xff1f; 冯诺依曼体系结构 冯诺依曼体系结构是计算机体系结构的基本原理之一。它将程序和数据都以二进制形式存储&#xff0c;以相同的方式处理和存取。 上图是冯诺依曼体系结构的五大组成部…

Neo4j 数据库管理 数据备份与恢复(头歌)

文章目录 第1关&#xff1a;数据备份与恢复任务描述相关知识数据备份数据导入 编程要求测试说明答案测试前准备Cypher 代码数据备份与导入 第1关&#xff1a;数据备份与恢复 任务描述 本关任务&#xff1a;熟练掌握数据备份与恢复。 相关知识 为了完成本关任务&#xff0c;…

Jvm常见问题

1. 为什么用元空间替换永久代 避免OOM异常&#xff1a;永久代中存放了很多JVM需要的类信息&#xff0c;这些数据大多数是不会被清理的&#xff0c;所以Full GC往往无法回收多少空间。但在元空间模型中&#xff0c;由于字符串常量池已移至堆外&#xff0c;且元空间的大小不受JV…

Elasticsearch:使用 ILM 示例运行降采样 (downsampling)

如果你对降采样还不是很熟的话&#xff0c;请阅读之前的文章 “Elasticsearch&#xff1a;对时间序列数据流进行降采样&#xff08;downsampling)”。这是一个简化的示例&#xff0c;可让你快速了解降采样如何作为 ILM 策略的一部分来减少一组采样指标的存储大小。 该示例使用典…

LeetCode | 二叉树的最大深度

LeetCode | 二叉树的最大深度 OJ链接 这里需要注意的一点是每次有返回值&#xff0c;需要定义变量来保存上一次的值最后取最高的一方加1 int maxDepth(struct TreeNode* root) {if(root NULL)return NULL;int left maxDepth(root->left);int right maxDepth(root->r…

C语言中一些有关字符串的常见函数的使用及模拟实现(1)

在编程的过程中&#xff0c;我们经常要处理字符和字符串&#xff0c;为了⽅便操作字符和字符串&#xff0c;C语⾔标准库中提供了 ⼀系列库函数&#xff0c;接下来我们就学习⼀下这些函数。 文章目录 strlen函数的使用及模拟实现strcmp函数的使用及模拟实现strcpy函数的使用及代…

《C++PrimerPlus》第9章 内存模型和名称空间

9.1 单独编译 Visual Studio中新建头文件和源代码 通过解决方案资源管理器&#xff0c;如图所示&#xff1a; 分成三部分的程序&#xff08;直角坐标转换为极坐标&#xff09; 头文件coordin.h #ifndef __COORDIN_H__ // 如果没有被定义过 #define __COORDIN_H__struct pola…