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

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当中重定向的模拟实现的全部内容,希望能对大家有所帮助!

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

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

相关文章

基于springboot+vue的校园爱心捐赠互助管理系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 三、库表设计 四、论文 前言 随着经济水平和生活水平的提高在校大学生在校需要处理的物品也在不断增加&#xff0c;同时校园内还存在很多贫困生&#xff0c;可以通过线上平台实现资源的整合和二次利用&#xff0c;通过线上平台求助信…

LeetCode Python - 32.最长有效括号

目录 题目答案方法一&#xff1a;动态规划方法二&#xff1a;使用堆栈 运行结果方法一方法二 题目 给你一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号 子串的长度。 示例 1&#xff1a; 输入&#xff1a;s “(()”…

机器学习入门-小白必看

机器学习 1. 机器学习的基本概念与背景2. 机器学习的常用方法3.是否需要学习机器学习&#xff0c;机器学习已经过时了&#xff1f;&#xff1f;4. 如何在机器学习上进行创新&#xff1f;5. 我该用哪种机器学习方法&#xff0c;如何定下来呢&#xff1f;总结&#xff08;对小白的…

O2O:Actor-Critic Alignment for Offline-to-Online Reinforcement Learning

ICML 2023 Poster paper 1 Introduction O2O容易因为分布偏移导致策略崩溃&#xff0c;解决方法包括限制策略偏移计以及平衡样本采样等。然而这些方法需要求解分布散度或者密度比(density ratio)。为了避免这些复杂操作&#xff0c;本文并不采用以往AC方法对Q值进行变形&…

【笔记】Android 漫游定制SPN定制有关字段

一、SPN模块简介 【笔记】SPN和PLMN 运营商网络名称显示 Android U 配置 WiFiCalling 场景下PLMN/SPN 显示的代码逻辑介绍 【笔记】Android Telephony 漫游SPN显示定制&#xff08;Roaming Alpha Tag&#xff09; 二、相关配置字段 non_roaming_operator_string_array 是否…

关于yolov8的output0

关于yolov8的output0 // output0nvinfer1::IElementWiseLayer* conv22_cv2_0_0 convBnSiLU(network, weightMap, *conv15->getOutput(0), base_in_channel, 3, 1, 1, "model.22.cv2.0.0");nvinfer1::IElementWiseLayer* conv22_cv2_0_1 convBnSiLU(network, we…

Cesium 自定义Primitive - 圆

一、创作思路 1、创建一个自定义CustomPrimitive 2、然后根据两个点&#xff0c;生成圆 3、方便后期绘制圆 二、实现代码 1、在vue的包中加入turf. npm install turf/turf 1、创建一个CustomCirclePrimitive类,并加入更新的代码 export default class CustomCirclePrimitive …

StarRocks实战——贝壳找房数仓实践

目录 前言 一、StarRocks在贝壳的应用现状 1.1 历史的数据分析架构 1.2 OLAP选型 1.2.1 离线场景 1.2.2 实时场景 1.2.3 StarRocks 的引入 二、StarRocks 在贝壳的分析实践 2.1 指标分析 2.2 实时业务 2.3 可视化分析 三、未来规划 3.1 StarRocks集群的稳定性 3…

STM32:CAN功能板设计和调试

0前言 本文主要目的是&#xff0c;总结去年设计stm32-CAN板子过程中遇到的问题&#xff0c;分为keil嵌入式软件和嘉立创EDA设计两个部分。 1 STM32F1 CAN功能 keil expected a “}“ 问题在于&#xff0c;PCB使用芯片为stm32f103c8t6&#xff0c;下载程序时选择device默认此…

在cadence中导入工艺库和仿真状态的方法

在cadence中导入库和仿真状态的方法 一、在cadence中导入库 1、打开cadence的启动界面&#xff0c;如图 2、右键空白处&#xff0c;添加library 3、找到自己的库文件路径即可 二、在cadence中导入仿真状态 1.打开ADE L界面 2.选择好自己需要的状态&#xff0c;注意要取…

Leet code 1089 复写0

1、先找到最后一个数 比如示例1中答案的最后一个数是4 定义两个指针 dest 和 cur dest初始位置是-1 cur初始位置为 0 如果arr[cur]为非零元素 dest位置1 如果arr[cur]为零元素 dest位置2 直到cur<arr.size() 或者 dest>arr.size()-1 cur就是最后一个元素位置 2、…

Swing程序设计(11)动作事件监听器,焦点事件监听器

文章目录 前言一、事件监听器是什么&#xff1f;二、详细展开 1.动作事件监听器2.焦点事件监听器总结 前言 如果你是坚持从Swing程序第一篇看到了这里&#xff0c;恭喜你&#xff0c;Swing程序设计简单地落下了帷幕&#xff0c;关于Swing程序更深的了解&#xff0c;可以自行学习…

在Vue中根据Url下载地址生成二维码展示在界面上

最近来了一个新需求&#xff0c;就是在网页页面上点击按钮不在是直接下载app安装包&#xff0c;需要支持手机扫码下载app&#xff0c;避免他们需要先从电脑上下载&#xff0c;然后传到微信&#xff0c;然后手机从微信上下载下来&#xff0c;得了&#xff0c;需求就是根据后端传…

【Python】-----基础知识

注释 定义&#xff1a;让计算机跳过这个代码执行用三个单引号/双引号都表示注释信息&#xff0c;在Python中单引号与双引号没有区别&#xff0c;但必须是成对出现 输出与输入 程序是有开始&#xff0c;有结束的&#xff0c;程序运行规则&#xff1a;从上而下&#xff0c;由内…

稀碎从零算法笔记Day6-LeetCode:长度最小的子数组

前言&#xff1a;做JD的网安笔试题&#xff0c;结果查找子串&#xff08;单词&#xff09;这个操作不会。痛定思痛&#xff0c;决定学习滑动数组 题型&#xff1a;数组、双指针、滑动窗口 链接&#xff1a;209. 长度最小的子数组 - 力扣&#xff08;LeetCode&#xff09; 来…

ATFX汇市:油价回落之际加元币值走弱,USDCAD有望刷新年内新高

ATFX汇市&#xff1a;加元是商品货币&#xff0c;币值受到国际油价和精炼石油出口的显著影响。2022年3月份&#xff0c;国际油价达到130美元的峰值水平&#xff0c;随后开启回落走势&#xff0c;时至今日&#xff0c;最新报价在80美元下方&#xff0c;累计跌幅近40%。疲弱的油价…

【框架学习 | 第一篇】一篇文章快速入门MyBatis

文章目录 1.Mybatis介绍1.1Mybatis历史1.2Mybatis特点1.3与其他持久化框架对比1.4对象关系映射——ORM 2.搭建Mybatis2.1引入依赖2.2创建核心配置文件2.3创建表、实体类、mapper接口2.4创建映射文件2.4.1映射文件命名位置规则2.4.2编写映射文件2.4.3修改核心配置文件中映射文件…

基于springboot+vue的医疗报销系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

培训机构如何通过小魔推做高效短视频矩阵?

随着智能手机的普及和移动互联网的高速发展&#xff0c;短视频作为一种全新的媒介形式&#xff0c;迅速崛起并占领了大量用户的碎片化时间。从野蛮生长到全面流行&#xff0c;逐渐成为各行业引流获客的主战场之一。 各行各业都意识到了短视频平台的潜力&#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…