[ Linux ] 重定向的再理解,以及文件系统的理解、inode和软硬链接

news2024/12/23 13:41:24

在上篇文章我们了解了Linux中文件描述符和重定向以及缓冲区的理解,本篇文章我们要对了解一下重定向的再理解、文件系统以及引出inode的意义和软硬链接。

目录

0.重定向

0.1标准输出 标准错误

为什么perror2后面跟了一个success

1.inode

1.1 inode理解

一个inode如何和属于自己的内容关联起来呢?

文件名算文件的属性吗?

那么当我们删除一个文件的时候,操作系统做了什么?

我知道自己所处的目录,就能知道目录的inode吗?

1.2创建一个新文件的步骤

2.软硬链接

2.1 软硬链接的区别

2.2 软链接的用处

2.3硬链接的用处

那么硬链接有什么用呢?

2.4软硬链接的删除


0.重定向

在之前我们实现过一个简易的shell,但是我们当时实现的myshell是不支持重定向的,当我们执行的时候是不认识重定向的。而 ls -a -l 是一个命令 , '>'右边是一个文件。因此当我们在获取用户输入的时候要考虑到这个问题。

因此我们要对输入的字符串是否重定向要进行分析,这里我们以输出重定向为例。加入我们有一个用户输入的是

ls -a -l>log.txt

我们要将次命令转换为

ls -a -l\0log.txt

我们使用两个指针,一个指向前半部分的l,一个指向'\0'后面的log.txt,前半部分继续是下面的指令分析,而后半部分是打开文件以及做重定向相关的工作。

因此首先我们需要判断一下这个指令是否有重定向工作,判断完成后要进行文件操作。以下使我们添加了重定向之后的myshell代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

#define SEP " "
#define SIZE 128
#define NUM 1024

#define DROP_SPACE(s) do { while(isspace(*s)) s++;}while(0)

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

int g_redir_flag = NONE_REDIR;
char *g_redir_filename = NULL;

char command_line[NUM];
char* command_args[SIZE];

char env_buffer[NUM];
extern char** environ;

int ChangeDir(const char* new_path)
{
  chdir(new_path);
  return 0;//调用成功
}

void PutEnvInMyShell(char* new_env)
{
  putenv(new_env);
}

void CheckDir(char* commands)
{
  assert(commands);
  //只关心有没有 大于或者 小于符号
  char* start = commands;
  char* end = commands+strlen(commands);//end指向字符串的结束
  while(start < end)
  {
    if(*start == '>')
    {
        if(*(start+1) == '>')
        {
          //追加
          *start = '\0';
          start+=2;
          g_redir_flag = APPEND_REDIR;
          DROP_SPACE(start);
          g_redir_filename = start;
          break;
        }
        else
        {
          //ls -a -l > log.txt输出重定向
          *start = '\0';
          start++;
          DROP_SPACE(start);
          g_redir_flag = OUTPUT_REDIR;
          g_redir_filename = start;
          break;

        }
    }
    else if(*start == '<')
    {
      //输入重定向
      *start = '\0';
      start++;
      DROP_SPACE(start);
      g_redir_flag = INPUT_REDIR;
      g_redir_filename = start;
      break;
    }
    else
    {
      start++;
    }
  }
}

int main()
{
  //一个shell 本质上就是一个死循环
  while(1)
  {
    g_redir_flag = NONE_REDIR;
    g_redir_filename = NULL;
    //不关心获取这些属性的接口
    //1.显示提示符
    printf("[张三@我的主机名 当前目录]# ");
    fflush(stdout);
    //2.获取用户输入
    memset(command_line,'\0',sizeof(command_line)*sizeof(char));
    fgets(command_line,NUM,stdin);//获取 输入 stdin
    command_line[strlen(command_line) - 1] = '\0';//清空\n
    //printf("%s\n",command_line);
    
    // "ls -a -l>log.txt" -> "ls -a -l\0log.txt"
    CheckDir(command_line);

    //3."ls -l -a -i" --> "ls","-l","-a","-i" 字符串切分
    command_args[0] = strtok(command_line, SEP);
    int index = 1;
    //给ls添加颜色
    if(strcmp(command_args[0]/*程序名*/,"ls") == 0)
      command_args[index++] = (char*)"--color=auto";
    //strtok 截取成功 返回字符串起始地址
    //截取失败 返回NULL
    while(command_args[index++] = strtok(NULL,SEP));
    // for debug
    //for(int i = 0;i<index;++i) 
    //{
    //  printf("%d:%s\n",i,command_args[i]);
    //}
    
    //4.TODO
    //如果直接exec*执行cd,最多只是让子进程进行路径切换,
    //子进程是一运行就完毕的进程!我们在shell中,更希望
    //父进程的路径切换
    //如果有些行为必须让父进程shell执行,不想让子进程
    //这种情况下不能创建子进程,只能让父进程自己实现对应的代码
    //这部分由父shell自己执行的命令称之为内建命令
    if(strcmp(command_args[0],"cd") == 0 && command_args[1] != NULL)
    {
      ChangeDir(command_args[1]);
      continue;
    }

    if(strcmp(command_args[0],"export") == 0 && command_args[1] != NULL)
    {
      strcpy(env_buffer,command_args[1]);
      PutEnvInMyShell(env_buffer);
      continue;
    }

    //5.创建子进程
    pid_t id = fork();
    if(id == 0)
    {
      int fd = -1;
      switch (g_redir_flag)
      {
        case NONE_REDIR:
          break;
        case INPUT_REDIR:
          fd = open(g_redir_filename,O_RDONLY);
          dup2(fd,0);
          break;
        case OUTPUT_REDIR:
          fd = open(g_redir_filename,O_WRONLY | O_CREAT | O_TRUNC);
          dup2(fd,1);
          break;
        case APPEND_REDIR:
          fd = open(g_redir_filename,O_WRONLY | O_CREAT | O_APPEND);
          dup2(fd,1);
          break;
        default:
          printf("BUG?\n");
          break;
      }
      //child
      //6.程序替换 会影响曾经子进程打开的文件吗?  不影响
      execvp(command_args[0],/*里面保存的就是执行的名字*/command_args);

      exit(1);//执行到这里一定失败了
    }
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(ret>0)
    {
      printf("等待子进程成功: sig:%d, code:%d\n",status&0x7F,(status>>8)&0xFF);
    }
  }

  return 0;
}

此时这段代码完成了重定向的添加。

0.1标准输出 标准错误

接下来我们看看这段代码

#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
    //stdout
    printf("hello printf1\n");
    fprintf(stdout,"hello fprintf1\n");
    fputs("hello fputs1\n",stdout);

    //stderr
    fprintf(stderr,"hello fprintf2\n");
    fputs("hello fputs2\n",stderr);
    perror("hello perror2");

    //cout
    cout<<"hello cout1"<<endl;

    //cerr
    cerr<<"hello cerr2"<<endl;

    return 0;
}

我们看看他的执行结果

当我们重定向到文件时,我们惊奇的发现,后面带1的都不见了,我们再cat 一下该文件

因此我们发现,虽然标准输出标准错误都是显示器文件,意思就是我们虽然都打印到了显示器内,但依旧是通过不同的文件描述符。因此当我重定向的时候,fd不同,当然是互不干扰的。

那么这里发现,这样的意义是什么呢?为什么要这么干?

这么干最好的意义是那些是程序日常哪些是程序错误。以便于当我们指向查看程序错误的时候便可以很方便的查看。---日志信息

如果我们想把所有的输出信息打印在一个文件内,我们可以这样输出

为什么perror2后面跟了一个success

在上面的输出我们发现hello perror2后面跟了一个Success ,但是我们的代码里面却没有说明呀,这是什么东西呢?因此我们不得不了解一下了

#include <iostream>
#include <cstdio>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
using namespace std;
int main()
{
    //
    int fd = open("log.txt",O_RDONLY);//这个方法必定失败的
    if(fd < 0 )
    {
        perror("open");
        
        return 1;
    }
    return 0;
}

那我们自己实现一个perror

#include <iostream>
#include <cstdio>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
using namespace std;

void my_perror(const char* info)
{
    fprintf(stderr,"%s: %s\n",info,strerror(errno));
}

int main()
{
    //
    int fd = open("log.txt",O_RDONLY);//这个方法必定失败的
    if(fd < 0 )
    {
        //perror("open");
        my_perror("my open");
        return 1;
    }
	return 0;
}

1.inode

1.1 inode理解

为了能解释清楚inode我们先简单了解一下文件系统,下图为磁盘文件系统图,磁盘是典型的块设备,磁盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。下图中的BootBlock是启动块。

  1. Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相 同的结构组成。
  2. 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
  3. GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
  4. inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用
  5. i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
  6. 数据区:存放文件内容

  1. Data blocks:以块为单位,进行文件内容的保存。大小4KB(4nKB)
  2. inode table:以128字节为单位,进行inode属性的保存,主要用来保存文件的属性。inode属性里面有一个inode编号!具有唯一性,一般而言,一个文件一个inode号。

  1. Block Bitmap:记录那个数据块已经被占用,那个数据块没有被占用。0表示没有被占用1表示已经被占用。因此我们使用比特位的内容表示是否被占用。
  2. inode Bitmap:由于inode是不可以重复的,并且inode Bitmap表示的是inode的使用情况。那么有多少inode,起始的inode编号,有多少个inode被使用,有多少block被使用,还剩多少,你的总group大小是多少。因此我们用一个Grop Descriptor Table(GDT)块组描述符来保存块组属性信息。
  3. Super Block(SB):存放文件系统本身的结构信息,是文件系统的顶级结构。

一个inode如何和属于自己的内容关联起来呢?

在inode Table内部包括问价的所有属性,其中会有一个blocks数组,其中直接保存的就是改文件对应的blocks编号!通过这个编号就可以直接找到自己的文件内容。

文件名算文件的属性吗?

答案是当然算,但是inode里面,并不保存文件名,其实在Linux下,底层实际都是通过inode编号来标识文件的!要找到文件,必须找到文件的inode编号 !那么谁来帮我找inode编号呢?那么目录是文件吗??答案当然也是,因此目录一定也有自己的inode,那么目录的数据块放什么?之前我们提到过,进入一个目录需要执行权限。创建一个文件需要w权限,查看文件名需要r权限。那么为什么? 这是因为目录文件内存的是inode编号的映射关系!

因此Linux同一个目录下是不能创建同名文件的,因为文件名本身就是一个具有Key值的东西!

创建一个文件的时候,一定是在一个目录下!!我们拿到新建文件的inode,找到自己所处的目录,根据目录的inode找到目录的dataBlock,将文件名和inode编号的映射关系写入到目录的数据块中!

那么当我们删除一个文件的时候,操作系统做了什么?

其实也是非常简单的,当我们删一个文件的时候,在inode位图下将1置为0就可以了。将标记改文件的内容和属性将1置为0即可。那么操作系统有没有真正的清除数据呢?答案是没有的,我们只是将1置为0。

我知道自己所处的目录,就能知道目录的inode吗?

答案肯定是不能的,当我们知道自己的目录名是,我们想要知道该目录的inode时我们必须要到父目录去查找对应关系,因此在该目录下是不可以的。

1.2创建一个新文件的步骤

至此,当我们创建一个新文件时,操作系统会做一下步骤:

  1. 存储属性:内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
  2. 存储数据:该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
  3. 记录分配情况 :文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录

新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文

件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

2.软硬链接

在我们之前所提到过的当我们查询该目录下的文件时,一个文件或者目录会有很多属性,那么下面这个1/2这一列表示的是什么呢?-- 这一列答案是硬链接数

当我们想读到一个文件更多的属性时,我们可以使用stat 文件名

那么我们如何创建一个文件的软硬链接呢?

我们可以使用下面的命令

ln -s 文件名 链接文件名 (创建软连接) 
ln 文件名 链接文件名 (创建硬链接)

2.1 软硬链接的区别

通过我们查找一下能够看到什么呢?

  • 软链接是一个独立文件,有自己的inode和inode编号
  • 硬链接不是一个独立文件,他和目标文件使用同一个inode

2.2 软链接的用处

那么软连接有什么用呢? 软链接就相当于Linux下的快捷方式!

加入我们在dir4目录下写了一个C语言程序,我们会到11-11目录下想执行这个程序时,我们必须带上路径,因此我们可以在11-11目录下创建一个该C语言程序的软连接,我们便可以在11-11路径下直接运行该软连接了。因此,Linux下软链接就和快捷方式一样。

那么软连接的文件内容是什么?保存的是指向文件的所在路径!

2.3硬链接的用处

那么硬链接是什么东西呢,我们刚知道硬链接使用的inode还链接的是原来文件的inode,因此硬链接文件就是单纯的在Linux指定的目录下给指定文件新增文件名和inode编号的映射关系!

当我们给一个文件创建一个硬连接时,我们发现这个数字变化了,所以至少可以证明,这个数字是改文件的赢连接数。那么什么是硬链接数?其实这里inode编号不就是一个“指针”的概念吗?因此硬连接的本质就是该文件inode属性中的一个计数器count,简而言之就是有几个文件名指向我的inode.

那么硬链接有什么用呢?

我们可以看到一个现象,为什么创建普通文件默认硬链接数是1,目录是2

因为普通的文件名本身就和自己的inode具有映射关系而且只有一个。任何一个目录里面会存在 .和..的隐藏文件,因此一个目录的本身的文件名和自己有映射关系,文件内部的.文件也和改文件有映射关系。因此默认一个目录的硬链接数是2。而..文件是上级目录的inode.

2.4软硬链接的删除

我们使用unlink 文件名 可以直接删除链接文件

(本篇完)

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

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

相关文章

一文吃透JavaScript中的DOM知识及用法

文章目录一、前言二、DOM框架三、认识DOM节点四、JS访问DOM1、获取节点2、改变 HTML3、改变 CSS4、检测节点类型5、操作节点间的父子及兄弟关系6、操作节点属性7、创建和操作节点五、快速投票一、前言 DOM&#xff1a;Document Object Model&#xff08;文档对象模型&#xff0…

指针的进阶应用之双指针、三指针

在牛客网和leetcode等网站刷题的过程中&#xff0c;时常会遇到一些使用双指针和三指针解决问题的实例。今天&#xff0c;我来介绍这两种方法&#xff0c;相信你会对指针的应用会提高一个档次。 目录移除元素删除有序数组中的重复项合并两个有序数组在下面的讲解的过程中&#x…

初步认识系统调用

目录前言一、什么是进程&#xff1f;1、进程与程序的区别&#xff1f;2、什么是进程的控制块二、什么是系统调用&#xff1f;三、认识几个比较简单的系统调用接口1、查看进程2、获取进程的pid/ppid(1).getpid/getppid(2)getpid/getppid的使用3、创建进程的方法总结前言 之前我们…

Redis系列:Redis持久化机制与Redis事务

Redis 是个基于内存的数据库。那服务一旦宕机&#xff0c;内存中数据必将全部丢失。所以丢失数据的恢复对于 Redis 是十分重要的&#xff0c;我们首先想到是可以从数据库中恢复&#xff0c;但是在由 Redis 宕机时&#xff08;说明相关工作正在运行&#xff09;且数据量很大情况…

LeetCode刷题记录01

1704判断字符串的两半是否相似序题目我的思路我的代码提交结果其他解简述思路提交结果总结序 我的日常碎碎念&#xff1a;今天下班在看综艺&#xff0c;看到群里班长开了个会议&#xff0c;于是决定开始学习&#xff0c;他说今天的每日一题好简单&#xff0c;让我也去刷一下。嗯…

数组的定义与使用

文章目录数组的基本概念为什么要使用数组什么是数组数组的创建及初始化数组的创建数组的初始化数组的使用数组中元素的访问遍历数组数组是引用类型基本数据类型与引用类型变量的区别认识null数组的应用场景保存数据作为函数的参数参数传基本数据类型参数传数组类型作为函数的返…

TFN T6300A 网络综合测试仪 以太网数据 千兆以太网测试仪 OTDR E1 PRI V.35/V.24 光功率计一体机

一款功能强大、便携式、方便使用、价格便宜的高性价比手持式以太网测试仪是企业中网络管理和维护人员的刚需仪器。好的以太网测试仪可以帮助工作人员迅速解决网络不通、网速慢、丢包、延迟等问题。 当今以太网测试仪市场参差不齐&#xff0c;说的功能一个比一个强&#xff0c;…

你的第一个基于Vivado的FPGA开发流程实践——二选一多路器

你的第一个基于Vivado的FPGA开发流程实践——二选一多路器 1 原理图 2 开发流程 首先我们先打开安装好的Vivado软件 创建一个文件 选择你的开发板 创建一个源文件 现在我们就可以根据原理使用Verilog代码实验这个功能了 module mux2( //端口列表a,b,sel,out);//交代端口类…

RabbitMQ第二个实操小案例——WorkQueue

文章目录RabbitMQ第二个实操小案例——WorkQueueRabbitMQ第二个实操小案例——WorkQueue 讲第二个案例之前&#xff0c;我们先看下前面第一个案例的模型&#xff1a; 可以看到&#xff0c;我们只有一个发布者和一个消费者&#xff0c;通过Queue队列&#xff0c;实现最简单的消…

02 【nodejs开发环境安装】

02 【nodejs开发环境安装】 1.版本介绍 在命令窗口中输入 node -v 可以查看版本0.x 完全不技术 ES64.x 部分支持 ES6 特性5.x 部分支持ES6特性&#xff08;比4.x多些&#xff09;&#xff0c;属于过渡产品&#xff0c;现在来说应该没有什么理由去用这个了6.x 支持98%的 ES6 特…

e智团队实验室项目-第三周-经典的卷积神经网络的学习

e智团队实验室项目-第三周-卷积神经网络的学习 赵雅玲 *, 张钊* , 李锦玉&#xff0c;迟梦瑶&#xff0c;贾小云&#xff0c;赵尉&#xff0c;潘玉&#xff0c;刘立赛&#xff0c;祝大双&#xff0c;李月&#xff0c;曹海艳&#xff0c; (淮北师范大学计算机科学与技术学院&am…

【树莓派不吃灰】基础篇⑱ 从0到1搭建docker环境,顺便安装一下emqx MQTT Broker、HomeAssistant、portainer

目录1. 前言2. 搭建docker环境3. docker简介3.1 docker解决什么问题&#xff1f;3.2 docker VS vm虚拟机3.2.1 vm虚拟机3.2.2 docker3.3 docker如何解决问题&#xff1f;3.4 docker运行架构3.4.1 镜像 : image3.4.2 容器 : container3.4.3 仓库 : repository3.5 国内镜像加速3.…

python easygui怎么修改默认按钮名字

1.执行以下代码找到easygui安装位置 import easyguiprint(easygui.__file__)2.打开上述路径下boxes文件夹下需要修改的组件 如此时想要修改选项栏的默认按钮名字 则打开choice_box.py文件 执行如下图 可以看到有Cancel、SelectALL、ClearALL、OK四个默认按钮&#xff0c;可否…

Linux下动静态库的制作与使用

学习导航一、关于动静态库的基本认识二、设计库的工程师角度(1)制作静态库(2)制作动态库二、使用库的用户角度(1)使用静态库(2)使用动态库三、理解的角度一、关于动静态库的基本认识 1.静态库 静态库以 .a 作为文件后缀程序在编译链接的时候&#xff0c;将静态库的代码拷贝到…

[TCP/IP] Linux 搭建服务器局域网

文章目录[TCP/IP] Linux 搭建服务器局域网1. 使用python内置库http.server2. 使用Http-Server[TCP/IP] Linux 搭建服务器局域网 1. 使用python内置库http.server python3: http.server 命令行启动&#xff1a; # python 3 python -m http.server 8000 # python 2 python -m S…

ZYNQ_FPGA_SPI通信协议多种实现方式

文章目录PLPSSPIGPIOAXI-GPIOAXI-Quad-SPI&#xff08;待测试&#xff09;本文记录一下在使用AD9363中的SPI通信问题&#xff0c;同时针对在ZYNQ系列开发板上实现SPI的方法做一个总结。ZYNQ系列包含了PL端和PS端&#xff0c;因为本科阶段有一定的ARM的开发经验&#xff0c;便想…

2022年,软件测试已经不吃香了吗?

最近因为疫情等各种原因&#xff0c;大厂裁员&#xff0c;失业等等频频受到关注。 不解释&#xff0c;确实存在&#xff0c;各行各业都很难&#xff0c;但是&#xff0c;说软件测试行业不吃香&#xff0c;我还真不认同&#xff08;不是为培训机构说好话&#xff0c;大环境不好…

JVM从入门到入魔,这份JVM必知必会的完整版带你彻底玩懂JVM

市面上各类 JVM 相关的资料虽多如牛毛&#xff0c;但是明显都很难让大家系统性地学明白&#xff0c;同时一线大厂技术面试现在 JVM 知识也是必考科目。 在大厂摸爬滚打 10 多年的 Java 高级技术专家全面梳理了系统化学习 JVM 的知识和经验&#xff0c;从入门到入魔&#xff0c…

位运算常用技巧以及练习

几个有趣的操作 利用或操作|和空格将英文字符转换成小写 // 可以变成小写i : a | fmt.Printf("%c\n", i)j : A | fmt.Printf("%c\n", j)利用与操作&和下划线把英文字符转换成大写 // 可以变成大写m : b & _n : B & _fmt.Printf("%c\n…

大数据ClickHouse进阶(二十七):ClickHouse服务监控

文章目录 ClickHouse服务监控 一、系统表 1、metrics 2、events 3、asynchronous_metrics