Linux 文件系列:深入理解文件描述符fd,重定向,自定义shell当中重定向的模拟实现

news2024/11/15 23:19:37

Linux 文件系列:深入理解文件fd,重定向,自定义shell当中重定向的模拟实现

  • 一.预备知识
  • 二.回顾C语言中常见的文件接口跟重定向建立联系
    • 1.fopen函数的介绍
    • 2.fclose函数的介绍
    • 3.代码演示
      • 1.以"w"(写)的方式打开
      • 2.跟输出重定向的联系
      • 3.以 "a"(追加)的方式打开
      • 4.跟追加重定向的联系
  • 三.认识并使用系统接口
    • 1.open
      • 1.open和fopen的联系(引出 FILE和struct file的联系)
    • 2.open的进一步介绍
    • 3.open函数的使用
      • 1.close函数
      • 2.开始使用并且看看这个fd到底是什么?
  • 四.理解文件描述符fd
    • 1.文件描述符fd的本质
    • 2.标准输入,标准输出,标准错误
    • 3.理解Linux下一切皆文件的设计理念
  • 五.理解struct file内核数据结构
  • 六.fd的分配规则
    • 1.先抛出结论
    • 2.代码演示
    • 3.替换标准输出时的现象
  • 七.理解重定向
    • 1.重定向的本质
    • 2.演示一下重定向
      • 1.输出重定向
      • 2.追加重定向
      • 3.输入重定向
        • 1.fread函数
        • 2.演示
  • 八.dup2函数:实现两个fd之间的重定向
    • 1.dup2实现输出重定向
    • 2.dup2实现追加重定向
    • 3.dup2实现输入重定向
  • 九.自定义shell当中重定向的模拟实现
    • 1.原myshell.c代码
    • 2.如何实现重定向
    • 3.定义全局变量
    • 4.检测是否要进行重定向的函数
    • 5.创建子进程进行程序替换的函数修改
    • 6.main函数的修改
    • 7.修改之后myshell.c代码
  • 十.stderr的作用
    • 1.介绍2>&1
    • 2.stderr的作用
    • 3.演示
  • 十一.重定向和程序替换之间是互不影响的

一.预备知识

在这里插入图片描述
经过刚才的分析,我们可以一个很重要的结论:

一个文件要被打开,一定要先在OS中形成被打开的文件对象

下面我们来回顾一下C语言中常见的文件接口
我们会发现重定向跟它们有所联系

二.回顾C语言中常见的文件接口跟重定向建立联系

关于C语言文件操作的详细内容,大家可以看我的这篇博客:
C语言文件操作详解

1.fopen函数的介绍

在这里插入图片描述
在这里插入图片描述

2.fclose函数的介绍

在这里插入图片描述

3.代码演示

1.以"w"(写)的方式打开

在这里插入图片描述

以"w"(写)的方式打开,如果文件不存在,就会在当前进程所在的路径当中创建它

在这里插入图片描述
创建成功
我们用vim写一些内容,再用w打开,看看w是否会清空之前的内容
在这里插入图片描述
在这里插入图片描述
清空成功

2.跟输出重定向的联系

在这里插入图片描述
在这里插入图片描述
我们会发现,fopen的"w"选项跟输出重定向很像啊
下面我们再来看看"a"选项的方式打开跟追加重定向的关系

3.以 “a”(追加)的方式打开

"a"也是写入,不过是从文件结尾处开始写入,是追加式写入,并不会清空文件

在这里插入图片描述
在这里插入图片描述
并没有清空原有内容

4.跟追加重定向的联系

在这里插入图片描述
在这里插入图片描述
我们会发现,fopen的"a"选项跟追加重定向很像啊

三.认识并使用系统接口

下面我们来认识并使用一下系统调用接口
首先我们达成1个共识:

C语言的文件操作接口,它的底层一定封装了系统调用接口

1.open

1.open和fopen的联系(引出 FILE和struct file的联系)

这是C语言提供的库函数:fopen:
在这里插入图片描述
这是系统调用接口:open:
在这里插入图片描述
可见,这个fd跟我们之前常用的FILE*指针很像啊,
其实它们的功能是一样的,C语言的FILE是一个结构体,这个结构体里面封装了fd
而这个fd是被打开的文件的结构体(struct file内核数据结构)中的一个属性,是用来区分不同文件的

2.open的进一步介绍

刚才我们还没有介绍第2个参数呢,下面我们来看一下
在这里插入图片描述
至此我们也理解了fopen是如何对open进行封装的
下面我们来使用一下open函数并且看看这个fd到底是啥啊?

3.open函数的使用

1.close函数

在这里插入图片描述

2.开始使用并且看看这个fd到底是什么?

在这里插入图片描述
在这里插入图片描述
现在我们有了两个问题:

  1. 0 1 2去哪了?
  2. 为什么会是 3 4 5 6?

下面就让我们借助这两个问题来深入理解一下文件描述符fd

四.理解文件描述符fd

1.文件描述符fd的本质

在这里插入图片描述

2.标准输入,标准输出,标准错误

在C语言的学习中我们都听说过

C语言程序(也就是进程),只要运行起来,默认就打开3个流

在这里插入图片描述
今天我们要说明的是:
在这里插入图片描述

3.理解Linux下一切皆文件的设计理念

在这里插入图片描述

五.理解struct file内核数据结构

在这里插入图片描述

六.fd的分配规则

1.先抛出结论

在这里插入图片描述

2.代码演示

分配规则1就不言而喻了,我们来验证分配规则2
在这里插入图片描述
我们先关闭stdin,然后在打开log.txt
如果该进程中log.txt被分配的fd是0,那么验证成功
在这里插入图片描述
验证成功

3.替换标准输出时的现象

下面我们先关闭stdout,然后再打开log.txt
在这里插入图片描述
在这里插入图片描述
为什么最后的

printf("log.txt的fd是: %d\n",fd);

没有成功打印呢?

因为stdout是标准输出流,是显示器对应的流,
我们平常printf是将字符串打印到stdout当中,但是我们在printf之前已经把stdout关掉了
所以不会打印到显示器

可是当我加了一行代码
在这里插入图片描述
cat log.txt之后
在这里插入图片描述
发现刚才printf中本来要往显示器上打印的数据现在写到了log.txt里面
这说明:

1.printf只认识stdout,也就是fd为1的文件

2.上层的fd并没有改变,但是底层fd指向的内容发生改变了
本来fd值为1的这个fd应该要指向显示器这个设备文件的
但是在这个进程当中  现在指向log.txt了

3.也就是说这个过程其实就是进行了一种类似于狸猫换太子式的指向的改变

七.理解重定向

1.重定向的本质

经由刚才的print的例子之后,我们可以发现:
由此可以得出重定向的本质:
在这里插入图片描述

重定向的本质,其实就是修改特定文件fd的指向

2.演示一下重定向

1.输出重定向

这是log.txt之前的数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出重定向成功

2.追加重定向

在这里插入图片描述
在这里插入图片描述
追加重定向成功

3.输入重定向

要进行输入重定向,我们要使用fread函数

1.fread函数

在这里插入图片描述

2.演示

在这里插入图片描述
在这里插入图片描述
输入重定向成功

八.dup2函数:实现两个fd之间的重定向

其实库里面给我们提供了一个函数dup2
可以实现两个fd之间的重定向
下面我们使用dup2函数再来演示一下重定向
在这里插入图片描述

1.dup2实现输出重定向

在这里插入图片描述
在这里插入图片描述
此时log.txt的fd是3

2.dup2实现追加重定向

在这里插入图片描述
在这里插入图片描述
实现成功

3.dup2实现输入重定向

在这里插入图片描述
在这里插入图片描述
实现成功

九.自定义shell当中重定向的模拟实现

经过上面的练习之后,下面我们修改一下我们的myshell.c代码,模拟实现一下重定向
关于myshell.c代码的实现,大家可以看我的博客当中的
Linux自定义shell的编写,里面实现了自定义shell

1.原myshell.c代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//#define DEBUG 1
#define SEP " "

char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码

char env[1024][1024]={'\0'};
int my_index=0;

const char* getUsername()
{
    const char* username=getenv("USER");
    if(username==NULL) return "none";
    return username;
}

const char* getHostname()
{

    const char* hostname=getenv("HOSTNAME");
    if(hostname==NULL) return "none";
    return hostname;
}

const char* getPwd()
{

    const char* pwd=getenv("PWD");
    if(pwd==NULL) return "none";
    return pwd;
}

//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{
    int i=0;
    usercommand[i++]=strtok(command,SEP);
    while(usercommand[i++]=strtok(NULL,SEP));
}

//解析命令行
void GetCommand(char* command,char* usercommand[])
{
    command[strlen(command)-1]='\0';//清理掉最后的'\0'
    CommandSplit(usercommand,command);
#ifdef DEBUG
    int i=0;
    while(usercommand[i]!=NULL)
    {
        printf("%d : %s\n",i,usercommand[i]);
        i++;
    }
#endif
}

//创建子进程,完成任务
void Execute(char* usercommand[])
{
    pid_t id=fork();
    if(id==0)
    {
        //子进程执行部分
        execvp(usercommand[0],usercommand);
        //如果子进程程序替换失败,已退出码为1的状态返回
        exit(1);
    }
    else
    {
        //父进程执行部分
        int status=0;
        //阻塞等待
        pid_t rid=waitpid(id,&status,0);
        if(rid>0)
        {
            lastcode=WEXITSTATUS(status);
        }
    }
}

void cd(char* usercommand[])
{
    chdir(usercommand[1]);
    char tmp[1024]={'\0'};
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
    lastcode=0;
}   

int echo(char* usercommand[])
{
    //1.echo后面什么都没有,相当于'\n'
    if(usercommand[1]==NULL)
    {
        printf("\n");
        lastcode=0;
        return 1;
    }
    //2.echo $?  echo $PWD echo $
    char* cmd=usercommand[1];
    int len=strlen(cmd);
    if(cmd[0]=='$' && len>1)
    {
        //echo $?
        if(cmd[1]=='?')
        {
            printf("%d\n",lastcode);
            lastcode=0;
        }
        //echo $PWD
        else
        {
            char* tmp=cmd+1;
            const char* env=getenv(tmp);
            //找不到该环境变量,打印'\n',退出码依旧为0
            if(env==NULL)
            {
                printf("\n");
            }
            else
            {
                printf("%s\n",env);
            }
            lastcode=0;
        }
    }
    else
    {
        printf("%s\n",cmd);
    }
    return 1;
}

void export(char* usercommand[])
{
    //export
    if(usercommand[1]==NULL)
    {
        lastcode=0;
        return;
    }
    strcpy(env[my_index],usercommand[1]);
    putenv(env[my_index]);
    my_index++;
}

int doBuildIn(char* usercommand[])
{
    //cd
    if(strcmp(usercommand[0],"cd")==0)
    {
        if(usercommand[1]==NULL) return -1;
        cd(usercommand);
        return 1;
    }
    //echo
    else if(strcmp(usercommand[0],"echo")==0)
    {
        return echo(usercommand);
    }
    //export
    else if(strcmp(usercommand[0],"export")==0)
    {
        export(usercommand);
    }
    return 0;
}

int main()
{
    while(1)
    {
        //1.打印提示符信息并获取用户的指令
        printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());
        char command[1024]={'\0'};
        fgets(command,sizeof(command),stdin);
        char* usercommand[1024]={NULL};
        //2.解析command字符串,放入usercommand指针数组当中
        GetCommand(command,usercommand);
        //3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0
        int flag=doBuildIn(usercommand);
        //返回值!=0说明是内建命令,无需执行第4步
        if(flag!=0) continue;
        //4.创建子进程,交由子进程完成任务
        Execute(usercommand);
    }
    return 0;
}

2.如何实现重定向

以输出重定向为例:

指令 > log.txt

输出重定向的作用其实就是把本来应该往显示器上打印的内容打印到了log.txt上
也就是说进行输出重定向的话,我们的log.txt就替代了显示器的位置
也就是说执行指令之前我们只需要执行一个
dup2(fd,1)即可
fd是log.txt的文件描述符,1是显示器的文件描述符

也就是说对于用户输入的一个完整的指令
例如:

ls -a -l > log.txt

在这里插入图片描述
我们要做的是:
1.检测是否需要进行重定向(检测指令当中是否有> 或者 >> 或者<)
2.如果需要,把这个指令拆分为两部分
“ls -a -l"和"log.txt”
后半部分是重定向到哪个文件当中
前半部分是真正的指令
如何拆分呢?把>改为’\0’,>>改为’\0’>,<改为’\0’即可

注意:

ls -a -l >                        log.txt

这样写也是可以的,因此我们要取出log.txt的时候要跳过空格

3.定义全局变量

第一步:
我们定义全局变量redir和四个宏常量,文件名和跳过空格的宏
在这里插入图片描述
在这里插入图片描述
注意:
在解析命令行之前就要检测是否要进行重定向
因为如果要进行重定向,就会对命令行进行拆分,拆分之后的指令才是真正要执行的指令

在后续执行指令时只需要根据全局变量redir是否是NoneRedir来判断是否要进行重定向
如果要进行重定向,根据redir具体的值来判断要进行输出/追加/输入重定向
进而判断filename的打开方式和dup2要覆盖显示器还是键盘

然后分类打开和覆盖即可

4.检测是否要进行重定向的函数

//跳过空格的宏
#define SKIP_SPACE(pos) do{ while(isspace(*pos)) pos++; }while(0)

//检测是否要进行重定向
void CheckRedir(char* command)
{
    int len=strlen(command);
    char* start=command,*end=command+len-1;
    while(end>=start)
    {
        //输入重定向
        //cat < log.txt
        if(*end=='<')
        {
            *end='\0';
            filename=end+1;
            SKIP_SPACE(filename);
            redir=InputRedir;
            break;
        }
        else if(*end=='>')
        {
            //追加重定向
            //ls -a -l >> log.txt
            if(end>start && *(end-1)=='>')
            {
                *(end-1)='\0';
                filename=end+1;
                SKIP_SPACE(filename);
                redir=AppendRedir;
                break;
            }
            //输出重定向
            else
            {
                *end='\0';
                filename=end+1;
                SKIP_SPACE(filename);
                redir=OutPutRedir;
                break;
            }
        }
        else
        {
            end--;
        }
    }
}

在这里插入图片描述
在这里我们就只演示非内建命令的重定向操作了
因为只演示非内建命令就能够做到让大家很好地去理解重定向了

5.创建子进程进行程序替换的函数修改

在这里插入图片描述

//创建子进程,完成任务
void Execute(char* usercommand[])
{
    pid_t id=fork();
    if(id==0)
    {
        //检测是否要进行重定向
        int fd=0;
        //输出重定向
        if(redir==OutPutRedir)
        {
            fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
            dup2(fd,1);
        }
        //追加重定向
        if(redir==AppendRedir)
        {
            fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
            dup2(fd,1);
        }
        //输入重定向
        if(redir==InputRedir)
        {
            fd=open(filename,O_RDONLY);
            dup2(fd,0);
        }
        //子进程执行部分
        execvp(usercommand[0],usercommand);
        //如果子进程程序替换失败,已退出码为1的状态返回
        exit(1);
    }
    else
    {
        //父进程执行部分
        int status=0;
        //阻塞等待
        pid_t rid=waitpid(id,&status,0);
        if(rid>0)
        {
            lastcode=WEXITSTATUS(status);
        }
    }
}

6.main函数的修改

在这里插入图片描述

int main()
{
    while(1)
    {
        redir=NoneRedir;
        filename=NULL;
        //1.打印提示符信息并获取用户的指令
        printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());
        char command[1024]={'\0'};
        fgets(command,sizeof(command),stdin);
        command[strlen(command)-1]='\0';//清理掉最后的'\n'
        //2.检测重定向
        CheckRedir(command);
        char* usercommand[1024]={NULL};
        //3.解析command字符串,放入usercommand指针数组当中
        GetCommand(command,usercommand);
        //4.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0
        int flag=doBuildIn(usercommand);
        //返回值!=0说明是内建命令,无需执行第4步
        if(flag!=0) continue;
        //5.创建子进程,交由子进程完成任务
        Execute(usercommand);
    }
    return 0;
}

7.修改之后myshell.c代码

模拟实现重定向的目的是为了让我们更好地去理解重定向
因此本次实现重定向只是简单的模拟实现,跟系统的重定向并不完全相同

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

#define SEP " "

#define NoneRedir 0
#define OutPutRedir 1
#define AppendRedir 2
#define InputRedir 3
int redir=NoneRedir;

char* filename=NULL;

char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码

char env[1024][1024]={'\0'};
int my_index=0;

const char* getUsername()
{
    const char* username=getenv("USER");
    if(username==NULL) return "none";
    return username;
}

const char* getHostname()
{

    const char* hostname=getenv("HOSTNAME");
    if(hostname==NULL) return "none";
    return hostname;
}

const char* getPwd()
{

    const char* pwd=getenv("PWD");
    if(pwd==NULL) return "none";
    return pwd;
}

//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{
    int i=0;
    usercommand[i++]=strtok(command,SEP);
    while(usercommand[i++]=strtok(NULL,SEP));
}

//解析命令行
void GetCommand(char* command,char* usercommand[])
{
    if(strlen(command)==0) return;
    CommandSplit(usercommand,command);
#ifdef DEBUG
    int i=0;
    while(usercommand[i]!=NULL)
    {
        printf("%d : %s\n",i,usercommand[i]);
        i++;
    }
#endif
}

//创建子进程,完成任务
void Execute(char* usercommand[])
{
    pid_t id=fork();
    if(id==0)
    {
        //检测是否要进行重定向
        int fd=0;
        //输出重定向
        if(redir==OutPutRedir)
        {
            fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
            dup2(fd,1);
        }
        //追加重定向
        if(redir==AppendRedir)
        {
            fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
            dup2(fd,1);
        }
        //输入重定向
        if(redir==InputRedir)
        {
            fd=open(filename,O_RDONLY);
            dup2(fd,0);
        }
        //子进程执行部分
        execvp(usercommand[0],usercommand);
        //如果子进程程序替换失败,已退出码为1的状态返回
        exit(1);
    }
    else
    {
        //父进程执行部分
        int status=0;
        //阻塞等待
        pid_t rid=waitpid(id,&status,0);
        if(rid>0)
        {
            lastcode=WEXITSTATUS(status);
        }
    }
}

void cd(char* usercommand[])
{
    chdir(usercommand[1]);
    char tmp[1024]={'\0'};
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
    lastcode=0;
}   

int echo(char* usercommand[])
{
        //1.echo后面什么都没有,相当于'\n'
        if(usercommand[1]==NULL)
        {
            printf("\n");
            lastcode=0;
            return 1;
        }
        //2.echo $?  echo $PWD echo $
        char* cmd=usercommand[1];
        int len=strlen(cmd);
        if(cmd[0]=='$' && len>1)
        {
            //echo $?
            if(cmd[1]=='?')
            {
                printf("%d\n",lastcode);
                lastcode=0;
            }
            //echo $PWD
            else
            {
                char* tmp=cmd+1;
                const char* env=getenv(tmp);
                //找不到该环境变量,打印'\n',退出码依旧为0
                if(env==NULL)
                {
                    printf("\n");
                }
                else
                {
                    printf("%s\n",env);
                }
                lastcode=0;
            }
        }
        else
        {
            printf("%s\n",cmd);
        }
        return 1;
}

int doBuildIn(char* usercommand[])
{
    if(usercommand[0]==NULL) return 0;
    //cd
    if(strcmp(usercommand[0],"cd")==0)
    {
        if(usercommand[1]==NULL) return -1;
        cd(usercommand);
        return 1;
    }
    //echo
    else if(strcmp(usercommand[0],"echo")==0)
    {
        return echo(usercommand);
    }
    //export
    else if(strcmp(usercommand[0],"export")==0)
    {
        //export
        if(usercommand[1]==NULL)
        {
            lastcode=0;
            return 1;
        }
        strcpy(env[my_index],usercommand[1]);
        putenv(env[my_index]);
        my_index++;
    }
    return 0;
}

//跳过空格的宏
#define SKIP_SPACE(pos) do{ while(isspace(*pos)) pos++; }while(0)

//检测是否发生了重定向
void CheckRedir(char* command)
{
    int len=strlen(command);
    char* start=command,*end=command+len-1;
    while(end>=start)
    {
        //输入重定向
        //cat < log.txt
        if(*end=='<')
        {
            *end='\0';
            filename=end+1;
            SKIP_SPACE(filename);
            redir=InputRedir;
            break;
        }
        else if(*end=='>')
        {
            //追加重定向
            //ls -a -l >> log.txt
            if(end>start && *(end-1)=='>')
            {
                *(end-1)='\0';
                filename=end+1;
                SKIP_SPACE(filename);
                redir=AppendRedir;
                break;
            }
            //输出重定向
            else
            {
                *end='\0';
                filename=end+1;
                SKIP_SPACE(filename);
                redir=OutPutRedir;
                break;
            }
        }
        else
        {
            end--;
        }
    }
}

int main()
{
    while(1)
    {
        redir=NoneRedir;
        filename=NULL;
        //1.打印提示符信息并获取用户的指令
        printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());
        char command[1024]={'\0'};
        fgets(command,sizeof(command),stdin);
        command[strlen(command)-1]='\0';//清理掉最后的'\n'
        //2.检测重定向
        CheckRedir(command);
        char* usercommand[1024]={NULL};
        //3.解析command字符串,放入usercommand指针数组当中
        GetCommand(command,usercommand);
        //4.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0
        int flag=doBuildIn(usercommand);
        //返回值!=0说明是内建命令,无需执行第4步
        if(flag!=0) continue;
        //5.创建子进程,交由子进程完成任务
        Execute(usercommand);
    }
    return 0;
}

十.stderr的作用

首先先介绍一下2>&1这一语法

1.介绍2>&1

下面我们用fprintf来演示一下
在这里插入图片描述
在这里插入图片描述
如果我们现在就是想要把标准错误和标准输出都往显示器上打印呢?

./mycmd > log.txt 2>&1

在这里插入图片描述
又因为我们先把1重定向到log.txt中,再把2重定向到1中
因此就做到把2和1中的内容全都往log.txt中打印了

2.stderr的作用

我们平常学习编程的时候,程序写的并不大
程序运行时的错误信息和正常信息我们都统一往显示器上打印了
可是一旦程序特别大,要打印的信息特别多,此时区分显示器上的正常信息和错误信息就很麻烦了

而区分正常信息和错误信息之后就能够方便我们对错误信息进行统一排查,提高效率

因此标准输出的作用是:接收打印的正常信息
标准错误的作用是接收打印的错误信息

3.演示

还是刚才那份代码
现在我们想把正常信息重定向到log.txt中
错误信息重定向到log.txt.error中

./mycmd 1>log.txt 2>log.txt.error
把1重定向给log.txt
把2重定向给log.txt.error

在这里插入图片描述
注意:这样重定向时不能带空格
也就是说不能这样写:

./mycmd 1 > log.txt 2 > log.txt.error

在这里插入图片描述

十一.重定向和程序替换之间是互不影响的

为什么它们之间是互不影响的呢?
因为程序替换时改变的是进程结构体当中的页表中虚拟地址空间和物理地址空间的映射和进程地址空间中的相关属性

而重定向改变的是文件描述符表中fd的指向
两者互不影响

以上就是Linux 文件系列:深入理解文件fd,重定向,自定义shell当中重定向的模拟实现的全部内容,希望能对大家有所帮助!

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

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

相关文章

Vue3 配置 vite.config.js 解决跨域问题

Vue3 配置 vite.config.js 解决跨域问题 问题再现 Access to XMLHttpRequest at ‘http://localhost:8080/user/register’ from origin ‘http://localhost:5173’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested…

JS数组,if等结构语序

目录 浏览器的断点调试&#xff1a; 流程控制&#xff1a; 顺序流程控制&#xff1a;流程代码会逐行向下进行。 分支流程控制&#xff1a; IF语句&#xff1a; Switch语句&#xff1a; Switch和if的区别&#xff1a; 三元表达式&#xff1a; 循环&#xff1a; for循环…

麻省理工最新开发AI模型,让机器人实现自主规划路线

文 | BFT机器人 麻省理工学院的研究人员独具匠心地应用了人工智能来解决仓库中的机器人路径规划问题&#xff0c;以此缓解交通拥堵的难题。据该学院介绍&#xff0c;他们的团队开发了一种深度学习模型&#xff0c;其效率比传统的强随机搜索方法高出近四倍&#xff0c;极大地提…

设计模式:策略模式 ⑥

一、策略模式思想 简介 策略模式&#xff08;Strategy Pattern&#xff09;属于对象的行为模式。其用意是针对一组算法&#xff0c;将每一个算法封装到具有共同接口的独立的类中&#xff0c;从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。…

关于V5版本的echarts的引导线标签文字存在描边问题

1.如果存在描边&#xff1a;&#xff08;如图所示&#xff0c;炒鸡难受好吧&#xff0c;也不知道官方为什么这样初始化&#xff09; 2.只需在series的label中配置color:#FFF即可

Crossbar阵列的电路结构及其基本原理

忆阻器Crossbar阵列是一种先进的神经网络硬件实现技术&#xff0c;它利用忆阻器的物理特性来模拟神经网络中的突触连接&#xff0c;为人工智能和机器学习应用提供了一种高效、低能耗的计算平台。本文将深入探讨忆阻器Crossbar阵列的基本原理及其在Read&#xff08;读取&#xf…

YOLOv9独家原创改进|加入幽灵卷积Ghost Convolution模块,轻量化!

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、论文摘要 由于内存和计算资源有限&#xff0c;在嵌入式设备上部署卷积神经网络是困难的。特征图中的冗余是那些成功的细胞神经网络的一个重要特征…

机器人顶刊IJRR近期国人新作(2024)

一、IJRR简介 The International Journal of Robotics Research&#xff08;IJRR&#xff09;是机器人领域的高水平学术期刊&#xff0c;专注于发布关于机器人技术和相关领域的最新研究成果。IJRR创刊于1982年&#xff0c;是该领域的第一本学术刊物&#xff0c;2022-2023最新影…

MP2494图纸 国产替代型号SC72001宽工作输入电压范围:4.5V 至 80V

MP2494 是一款单片降压开关变换器。它在宽输入范围内可实现 2A 连续输出电流&#xff0c;具有出色的负载和线性调整率。其控制良好的开关沿降低了 EMI 干扰。故障保护功能包括逐周期限流保护和过温关断保护。MP2494 最大限度地减少了现有标准外部元器件的使用。MP2494采用SOIC8…

力扣每日一题 用栈实现队列

Problem: 232. 用栈实现队列 文章目录 思路复杂度&#x1f496; 朴素版&#x1f496; 优化版 思路 &#x1f468;‍&#x1f3eb; 路飞题解 复杂度 时间复杂度: 添加时间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) 空间复杂度: 添加空间复杂度, 示例&#xff1a; O ( …

非常优秀的一个开源库Gradio,几行代码完成部署快速搭建AI算法可视化部署演示,直接启动零配置实现微信分享、公网分享、内网穿透,包含项目搭建和案例分享

非常优秀的一个开源库Gradio&#xff0c;几行代码完成部署快速搭建AI算法可视化部署演示&#xff0c;直接启动零配置实现微信分享、公网分享、内网穿透&#xff0c;包含项目搭建和案例分享。 Gradio是一个功能丰富的Python库&#xff0c;可以让您轻松创建和共享自己的交互式机器…

【JAVA】Tomcat集成到IDEA

目录 1.在IDEA中安装插件&#xff1a;Smart Tomcat。 2.配置smart tomcat 浏览器显示中文出现乱码 我们可以借助IDEA的插件&#xff0c;把tomcat集成IDEA中&#xff0c;然后我们就可以通过IDEA一键式的重新打包部署了。 1.在IDEA中安装插件&#xff1a;Smart Tomcat。 1&a…

测试环境搭建整套大数据系统-问题篇(一:实时遇到的问题)

1. java.io.IOException: Failed to deserialize JSON ‘{“age”:867,“sex”:“fba8c074f9”,“t_insert_time”:“2024-03-04 14:12:24.821”}’ 解决方式 修改数据类型。将TIMESTAMP_LTZ改为TIMESTAMP。 2. java. lang,classNotFoundException: org,apache.flink,streami…

5G智能制造热力工厂数字孪生可视化平台,推进热力行业数字化转型

5G智能制造热力工厂数字孪生可视化平台&#xff0c;推进热力行业数字化转型。在当今这个信息化、数字化的时代&#xff0c;热力生产行业也迎来了转型的关键时刻。为了提升生产效率、降低成本、提高产品质量&#xff0c;越来越多的热力生产企业开始探索数字化转型之路。而5G智能…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:组件标识)

id为组件的唯一标识&#xff0c;在整个应用内唯一。本模块提供组件标识相关接口&#xff0c;可以获取指定id组件的属性&#xff0c;也提供向指定id组件发送事件的功能。 说明&#xff1a; 从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容…

【Sql Server】存储过程的创建和使用事务,常见运用场景,以及目前现状

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

【3GPP】【核心网】【5G】5G核心网协议解析(二)(超详细)

5G UE 附着过程 UE AMF ----------------- 注册请求(Registration Request) ----------------------> <--------------- 鉴权请求(Authentication Request) ------…

Kafka面经

1.Kafka如何保证消息不丢失 生产者&#xff1a; 1.Producer 默认是异步发送消息&#xff0c;这种情况下要确保消息发送成功&#xff0c;有两个方法 a. 把异步发送改成同步发送&#xff0c;这样 producer 就能实时知道消息发送的结果。 b. 添加异步回调函数来监听消息发送的结…

el-select下拉框选项分多列展示

前言 相信很多开发者在实际项目开发过程中都会使用到 element-ui 组件库中的 Select 选择器&#xff0c;这个选择器官方是默认只用一列去展示所有的选项信息&#xff0c;当我们需要展示的选项内容比较多时用户只能通过滚动条去查找&#xff0c;多少会影响操作效率&#xff0c;本…

redis02 安装

官网下载 传送门https://redis.io/download/#redis-downloads 安装Redis mac m1安装 下载你需要版本的软件包放到指定的目录下进行解压 cd 到解压好的redis目录 运行下面的命令进行编译测试 sudo make test 中途可能会提示你安装make工具&#xff0c;按提示安装即可&…