【Linux】文件操作、文件描述符和重定向

news2024/11/27 12:53:52

​🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉重新谈论文件👈
    • 👉回顾 C 语言的文件操作👈
    • 👉文件操作的系统调用👈
      • open
      • write
      • read
    • 👉文件的深入理解👈
      • 文件描述符
      • 重定向
        • 1. 输出重定向
        • 2. 追加重定向
        • 3. 输入重定向
      • myshell 实现重定向
      • Linux 下一切皆文件
    • 👉总结👈

👉重新谈论文件👈

  • 空文件也要占据磁盘空间。
  • 文件等于文件内容加文件属性。
  • 文件操作等于对文件内容的操作、对文件属性的操作或对文件内容和属性的操作。
  • 标识一个文件,必须使用文件路径加文件名(具有唯一性)。
  • 如果没有指明对应的文件路径,默认是在当前路径(进程的工作路径)进行文件访问。
  • 当我们把 fopen、fclose、fread 和 fwrite 等接口写完之后,代码编译链接形成可执行程序之后,没运行可执行程序,文件对应的操作也没有被执行。故对文件的操作,本质是进程对文件的操作。
  • 一个文件如果没有被打开,不可以进行文件访问。一个文件要被访问,就必须先被用户进程和操作系统打开。
  • 并不是所有的磁盘文件都被打开了,磁盘文件可分为两种:被打开的文件和没有被打开的文件。所以,文件操作的本质是研究进程和被打开文件的关系。(注:文件系统里研究没有被打开的文件)

👉回顾 C 语言的文件操作👈

C 语言有文件操作,C++ 也有文件操作,任何一门语言都会有文件操作,而这些语言的操作接口都不一样!因为这些语言的文件操作接口都不一样,学习的成本是挺高的。那如何降低学习成本呢?我们知道:文件是在磁盘里的,磁盘是硬件。所有人想访问磁盘就不能绕过操作系统,那么开发者就必须使用操作系统提供的文件级别的系统调用接口。所以无论上层语言如何变化,库函数底层实现都必须调用系统调用接口。那么库函数可以千变万化,但是底层是不变的。那我们学习不变的东西,就可以降低学习成本了。

现在我们来回顾一下 C 语言的文件操作接口,再来学习文件操作的系统调用接口。

以 w 的方式打开文件

注:fprintf 函数可以将格式化的数据写到指定的流中。以 w 的方式打开文件,文件不存在会自动创建;如果文件存在,先清空文件的内容再进行写入。

#include <stdio.h>

#define FILE_NAME "log.txt"

int main()
{
    // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
    // a(append,追加), a+(追加式写入)
    FILE* fp = fopen(FILE_NAME, "w");   // 没有指明路径,默认在当前路径进行文件操作
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    int cnt = 5;
    while(cnt)
    {
        fprintf(fp,"%s:%d\n", "hello world", cnt--);
    }

    return 0;
}

在这里插入图片描述

在这里插入图片描述

以 r 的方式打开文件

在这里插入图片描述

#include <stdio.h>
#include <string.h>

#define FILE_NAME "log.txt"

int main()
{
    // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
    // a(append,追加), a+(追加式写入)
    FILE* fp = fopen(FILE_NAME, "r");   // 没有指明路径,默认在当前路径进行文件操作
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    char buffer[64];
    // 读取sizeof(buffer)-1个字符,为\0留一个位置
    while(fgets(buffer, sizeof(buffer) - 1, fp) != NULL)
    {
        buffer[strlen(buffer) - 1] = '\0';  // 清除\n
        puts(buffer);
    }

    return 0;
}

在这里插入图片描述

在这里插入图片描述
以 a 的方式打开文件

#include <stdio.h>

#define FILE_NAME "log.txt"

int main()
{
    // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
    // a(append,追加), a+(追加式写入)
    FILE* fp = fopen(FILE_NAME, "a");   // 没有指明路径,默认在当前路径进行文件操作
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    int cnt = 5;
    while(cnt)
    {
        fprintf(fp,"%s:%d\n", "hello world", cnt--);
    }

    return 0;
}

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

打开文件的方式
在这里插入图片描述

如上就是我们之前学的文件相关操作。还有 fseek、ftell 和 rewind 等函数,在 C 语言部分已经学习过,大家可以自行复习一下。

👉文件操作的系统调用👈

open

上面所使用的的 C 语言文件操作函数都是通过调用相应的系统调用接口的,fopen 函数对应的是系统调用 open,fwrite 等函数对应的是系统调用 write,fclose 函数对应的是系统调用 close。

系统调用 open 的参数和返回值

  • 第一个参数:文件路径+文件名。只提供文件名,默认在当前路径进行文件操作。
  • 第二个参数:打开文件的方式。该参数是通过宏来表示不同的打开方式,如:O_RDONLY 只读方式打开文件,O_WRONLY 只写方式打开文件等。通过按位或可以实现不同的文件打开方式,原因是这些宏都是通过比特位的不同来标记不同的选项,也就是说一个比特位就是一个选项。需要注意的是,比特位的位置不能重复。
  • 第三个参数:创建文件的起始权限权限。为打开的文件设置不同的权限。使用 C 语言文件操作函数创建出来的文件默认权限是 664,文件的权限等于起始权限 & (~umask),普通文件的起始权限是 666,目录文件的起始权限是 777。
  • 成功打开文件时返回一个大于 0 的文件描述符,打开失败则返回 -1 并且设置错误码 errno。
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写

在这里插入图片描述

通过比特位来传递信息示例:

#include <stdio.h>

// 不同的标记位表示不同的选项
// 下面的每个宏对应的数值,只有一个比特位是1,彼此的位置不重叠
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)

void show(int flags)
{
    if(flags & ONE) printf("ONE\n");
    if(flags & TWO) printf("TWO\n");
    if(flags & THREE) printf("THREE\n");
    if(flags & FOUR) printf("FOUR\n");

}

int main()
{
    show(ONE);
    printf("--------------------\n");
    show(TWO);
    printf("--------------------\n");
    show(ONE | TWO);
    printf("--------------------\n");
    show(ONE | TWO | THREE);
    printf("--------------------\n");
    show(ONE | TWO | THREE | FOUR);
    
    return 0;
}

在这里插入图片描述

在这里插入图片描述
那么,系统调用 open 中的各种宏也是通过这种方式实现的。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>

#define FILE_NAME "log.txt"

int main()
{
    int fd = open(FILE_NAME, O_WRONLY);
    // assert(fd != -1);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    close(fd);  // close关闭文件,参数是对应的文件描述符

    return 0;
}

在这里插入图片描述
在这里插入图片描述
宏 O_WRONLY 只是写,没有对应的文件就打开失败,并不是没有对应的文件就自动创建。如果想要没有对应文件就自动创建,想要按位或上 O_CREAT。

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

现在虽然没有出错,但是创建出来的文件的权限却是全乱的。如果想要创建出来的文件的权限是不乱的,就需要传入 open 的第三个参数。

在这里插入图片描述

在这里插入图片描述

如果我们不想要系统默认的权限掩码,可以通过 umask 函数来设置。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


write

在这里插入图片描述

打印被打开文件的文件描述符

在这里插入图片描述

在这里插入图片描述

关于为什么被打开文件的文件描述符是 3,会在后面的内容里讲解,这也是埋下的一个小小的伏笔。

提示内容:在语言层面上,文件是分文本类文件和二进制类文件的;但再操作系统层面上,文件都是二进制的。

在这里插入图片描述

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

#define FILE_NAME "log.txt"

int main()
{
    umask(0);   // 将权限掩码设置为0,文件的最终权限等于起始权限&(~umask)
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    int cnt = 5;
    char outBuffer[64];
    while(cnt)
    {
        sprintf(outBuffer, "%s:%d\n", "hello world", cnt--);
        // C语言规定以\0作为字符串的结尾, 但与文件没有任何的关系, 所以下面的strlen不需要+1
        write(fd, outBuffer, strlen(outBuffer));
    }

    //printf("fd:%d\n", fd);

    close(fd);  // close关闭文件,参数是对应的文件描述符

    return 0;
}

在这里插入图片描述

在这里插入图片描述

如果我们上面的代码改成下面的样子,再运行起来并查看文件会出现上面情况呢?

在这里插入图片描述

在这里插入图片描述

可以看到:write 是覆盖式写入的,并不是先清空文件里的内容再进行写入。但是 C 语言的写入不是这样子的呀,C 语言的文件操作函数是对系统调用的封装。如果我们也想要实现文件存在时,先清空文件的内容再进行写入的话,还需要给 open 多传入一个宏 O_TRUNC。

在这里插入图片描述

在这里插入图片描述

所以,C语言的fopen(FILE_NAME, "w")对应的系统调用就是open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666)。如果想以追加的方式向文件写入的话,只需要再给 open 再传入一个宏 O_APPEND。注:O_APPEND 不要和 O_TRUNC 一起使用。

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

read

在这里插入图片描述

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

#define FILE_NAME "log.txt"

int main()
{
    umask(0);   // 将权限掩码设置为0,文件的最终权限等于起始权限&(~umask)
    //int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd = open(FILE_NAME, O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    char buffer[1024];
    ssize_t num = read(fd, buffer, sizeof(buffer) - 1); // 减1的原因是给\0留一个位置
    if(num > 0) buffer[num] = '\0';
    printf("%s", buffer);

	return 0;
}

在这里插入图片描述

在这里插入图片描述

库函数与系统调用的关系
在这里插入图片描述

👉文件的深入理解👈

文件描述符

在前面已经提到过:文件操作的本质就是进程和被打开文件的关系。进程是可以代开多个文件的,那么系统中一定会存在大量的被打开的文件的。这些被打开的文件,就要被操作系统管理起来。管理的本质是先描述再组织。操作系统为了管理对应的打开文件,必定要为文件创建对应的内核数据结构来表示文件,而这个内核数据结构就是struct file,其包含了文件的大部分属性。注:struct file和 C 语言的FILE不是一样的东西。

那接下来,我们就来学习进程是如何和被打开文件关联起来的!

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

// #的作用是将宏参数转化成字符串并与其他字符串连接起来
#define FILE_NAME(number) "log.txt"#number

int main()
{
    umask(0);   // 将权限掩码设置为0,文件的最终权限等于起始权限&(~umask)
    int fd0 = open(FILE_NAME(0), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd1 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd2 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd3 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd4 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);

    printf("fd0:%d\n", fd0);
    printf("fd1:%d\n", fd1);
    printf("fd2:%d\n", fd2);
    printf("fd3:%d\n", fd3);
    printf("fd4:%d\n", fd4);

    close(fd0);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);

    return 0;
}

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

看到上面连续的小整数,我想大家肯定能够想到数组的下标,那么我们可以猜测文件描述符可能与数组有关。那为什么是从 3 开始的呢?0、1、2 那哪去了?在学习 C 语言的时候,我们学到过 C 语言程序会默认打开三个流:stdin(标准输入流:键盘)、stdout(标准输出流:显示器)和 stderr(标准错误流:显示器)。这三个流的类型都是FILE*,而FILE是结构体。C 语言进行文件操作是使用的是FILE*,而操作系统使用的是文件描述符fd,那么结构体FILE中肯定包含文件描述符fd。所以 0、1、2 就被这三个流使用了。

在这里插入图片描述

写个程序来验证上面的说法

在这里插入图片描述

在这里插入图片描述

理解文件描述符的本质

在这里插入图片描述

文件描述符的本质是进程的文件描述符表的下表,也就是数组下标!!!进程与被打开文件的关系:进程通过文件描述符表指向对应的被打开的文件。

文件描述符的分配规则

按顺序从小到大查找文件描述符表,最小的且没有被占用的 fd 就会分配给被打开的文件。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    assert(fd != -1);

    printf("fd:%d\n", fd);
    close(fd);

    return 0;
}

在这里插入图片描述

在这里插入图片描述
关闭 0

在这里插入图片描述

在这里插入图片描述

关闭 2

在这里插入图片描述
在这里插入图片描述
关闭 0 和 2

在这里插入图片描述

在这里插入图片描述

重定向

那如果我们只把 1 关掉会怎么样呢?

在这里插入图片描述

在这里插入图片描述
将程序运行起来,我们可以发现并没有向显示器上打印信息。原因也非常的简单,因为我们把标准输出(显示器)给关掉了。又因为 printf 函数是向 stdout 上打印的,stdout 的 文件描述符为 1,而当前 1 号文件描述符执行的是我们自己创建的文件,所以数据就被打印到了该文件中了。注:需要刷新 stdout 才能看到信息。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果我们没有关掉 1,数据就会被打印到显示器上;而如果我们关掉了 1,数据就被打印到了文件里。那么这种现象就叫做重定向。常见的重定向:输出重定向>、追加重定向>>和输入重定向<重定向的本质是:上层使用的 fd 不变,在内核中更改 fd 对于的struct file*的地址

如果重定向先要关闭 1,才能进行重定向的话,这就有点挫了。系统为了支持我们更好地进行重定向,给我们提供了一个系统调用dup2

在这里插入图片描述

在这里插入图片描述

请简述重定向的实现原理:
每个文件描述符都是一个内核中文件描述信息数组的下标,对应有一个文件的描述信息用于操作文件,而重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件。

1. 输出重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    assert(fd != -1);

    dup2(fd, 1);    // 将fd的内容拷贝到1中
    printf("open fd:%d\n", fd); // printf -> stdout
    fprintf(stdout, "open fd:%d\n", fd);   // fprintf -> stdout 

    fflush(stdout); // 刷新缓冲区

    close(fd);

    return 0;
}

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

在这里插入图片描述

2. 追加重定向

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

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    assert(fd != -1);

    dup2(fd, 1);    // 将fd的内容拷贝到1中
    printf("open fd:%d\n", fd); // printf -> stdout
    fprintf(stdout, "open fd:%d\n", fd);   // fprintf -> stdout 

    const char* msg = "It's Crazy Thursday. Give me 50 yuan\n";
    write(1, msg, strlen(msg));

    fflush(stdout); // 刷新缓冲区

    close(fd);

    return 0;
}

在这里插入图片描述

在这里插入图片描述

3. 输入重定向

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

int main()
{
    int fd = open("log.txt", O_RDONLY);
    assert(fd != -1);

    dup2(fd, 0);    // 将fd的内容拷贝到0中
    
    char line[64];
    while(1)
    {
        printf("< ");
        // 读取结束退出while循环
        if(fgets(line, sizeof(line) - 1, stdin) == NULL)    
            break;
        printf("%s", line);
    }

    close(fd);

    return 0;
}

在这里插入图片描述

在这里插入图片描述

myshell 实现重定向

  • 因为命令是子进程执行的真正重定向的工作一定是子进程执行的
  • 如何重定向,是父进程要给子进程提供信息
  • 重定向不会影响父进程,因为进程具有独立性
  • 进行重定向时,子进程会发生写实拷贝,拷贝父进程的 PCB 和文件描述符表,再来修改自己的文件描述符表进行重定向

在这里插入图片描述

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

#define NUM 1024
#define OPT_NUM 64  // 命令行参数的最多个数
#define NONE_REDIR 0    // 无重定向
#define INPUT_REDIR 1   // 输入重定向
#define OUTPUT_REDIR 2  // 输出重定向
#define APPEDN_REDIR 3  // 追加重定向

// 过滤空格
#define trimSpace(start) do{ while(isspace(*start))  ++start; }while(0)

char lineCommand[NUM];
char* myargv[OPT_NUM];
// 上一个进程的退出信息
int lastCode = 0;
int lastSignal = 0;

int redirType = NONE_REDIR; // 重定向类型默认为无重定向
char* redirFile = NULL;     // 重定向的文件名

// "ls -a -l > myfile.txt" -> "ls -a -l" "myfile.txt"
void commandCheck(char* commands)
{
    // 重置重定向信息
    redirType = NONE_REDIR;
    redirFile = NULL;
    // 重置错误码
    errno = 0;

    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands);

    while(start < end)
    {
        if(*start == '>')
        {
            *start = '\0';
            ++start;
            if(*start == '>')
            {
                // "ls -a >> myfile.txt"
                redirType = APPEDN_REDIR;   // 追加重定向
                ++start;
            }
            else
            {
                // "ls -a > myfile.txt"
                redirType = OUTPUT_REDIR;   // 输出重定向
            }
            trimSpace(start);   // 过滤空格
            redirFile = start;
            break;
        }
        else if(*start == '<')
        {
            // "cat <     myfile.txt"
            *start = '\0';  // 将字符串分割成两部分
            ++start;
            trimSpace(start);   // 过滤空格
            // 填写重定向信息
            redirType = INPUT_REDIR;    // 输入重定向
            redirFile = start;
            break;
        }
        else
        {
            ++start;
        }
    }
}

int main()
{
    while(1)
    {
        char* user = getenv("USER");
        // 根据用户输出对应的提示信息, get_current_dir_name函数可以获得当前的工作路径
        if(strcmp(user, "root") == 0)
        {
            printf("[%s@%s %s]# ", user, getenv("HOSTNAME"), get_current_dir_name());
        }
        else
        {
            printf("[%s@%s %s]$ ", user, getenv("HOSTNAME"), get_current_dir_name());
        }
        fflush(stdout); // 刷新缓冲区
        
        // 获取用户输入
        char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
        assert(s != NULL);
        // 清除最后一个\n, abcd\n
        lineCommand[strlen(lineCommand) - 1] = 0;

        // 字符串切割:"ls -a -l" -> "ls" "-a" "-l"
        // "ls -a -l > myfile.txt" -> "ls -a -l" "myfile.txt"
        // "cat < myfile.txt" -> "cat" "myfile.txt"
        commandCheck(lineCommand);  // 如果有重定向,则将字符串拆成两部分
        myargv[0] = strtok(lineCommand, " ");
        int i = 1;
        // 因为无法执行"ll"指令, 所以这里做一下处理
        if(myargv[0] != NULL && strcmp(myargv[0], "ll") == 0)
        {
            myargv[0] = "ls";
            myargv[i++] = "-l";
        }
        if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
        {
            myargv[i++] = "--color=auto";
        }
        // 如果切割完毕, strtok返回NULL, myargv[end] = NULL
        while(myargv[i++] = strtok(NULL, " "));

        // 如果是cd命令, 不需要创建子进程来执行, 让当前进程的父进程shell执行对应的命令, 本质就是调用系统接口
        // 像这种不需要创建子进程来执行, 而是让shell自己执行的命令, 称为内建命令或者内置命令
        // echo和cd就是一个内建命令
        if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
        {
            // 如果cd命令没有第二个参数, 则切换到家目录
            if(myargv[1] == NULL)
            {
                chdir(getenv("HOME"));  // 更改到家目录
            }
            else
            {
                if(strcmp(myargv[1], "-") == 0) // 该功能还有BUG, 因为环境变量的问题
                {
                    chdir(getenv("OLDPWD"));    // 回到上一次所处的路径
                }
                else if(strcmp(myargv[1], "~") == 0)
                {
                    chdir(getenv("HOME"));  // 去到家目录
                }
                else
                {
                    chdir(myargv[1]);   // 更改到指定目录
                }
            }
            continue;   // 不创建子进程, continue回到while循环处
        }

        // 实现echo命令, 当前的echo命令功能也不是很全
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
        {
            if(strcmp(myargv[1], "$?") == 0)
            {
                printf("%d, %d\n", lastSignal, lastCode);
            }
            else
            {
                printf("%s\n", myargv[1]);
            }
            continue;
        }

        // 创建子进程来执行命令
        pid_t id = fork();
        assert(id != -1);

        // child process
        if(id == 0)
        {
            // 因为命令是子进程执行的,真正重定向的工作一定是子进程执行的
            // 如何重定向,是父进程要个子进程提供信息
            // 这里的重定向不会影响父进程,因为进程具有独立性
            switch(redirType)
            {
                case NONE_REDIR:
                    // 什么都不做
                    break;
                case INPUT_REDIR:
                    {
                        ssize_t fd = open(redirFile, O_RDONLY);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        // 重定向的文件已经成功打开了
                        dup2(fd, 0);
                    }
                    break;
                case OUTPUT_REDIR:
                case APPEDN_REDIR:
                    {
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == APPEDN_REDIR)   flags |= O_APPEND;
                        else flags |= O_TRUNC;
                        ssize_t fd = open(redirFile, flags, 0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(fd, 1);    // ls等指令执行结果是打印在显示器上的
                    }
                    break;
                default:
                    printf("error\n");
                    break;
            }
            execvp(myargv[0], myargv);  // 执行程序替换的时候,不会影响曾经进程打开的重定向的文件,因为程序替换只是替换代码和数据
            exit(errno);    // 进程替换失败
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);   // 阻塞等待
        assert(ret > 0);
        lastCode = ((status >> 8) & 0xFF);
        lastSignal = (status & 0x7F);
    }
    return 0;
}

myshell 重定向演示使用

在这里插入图片描述

当进程退出时,曾经被打开的文件会被关闭。

Linux 下一切皆文件

在之前的博客里说过:Linux 系统下一切皆文件。那 Linux 系统是如何做到一切皆文件的呢?我们又如何理解 Linux 下一切皆文件呢?见下图:

在这里插入图片描述

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

struct file 内包含引用计数,打开该文件的进程退了,则引用计数减减。当该计数为 0 时,操作系统才会释放这个被打开的文件。

在这里插入图片描述

文件的操作方法

在这里插入图片描述

👉总结👈

本篇博客主要讲解了文件操作的库函数和系统调用,深入了解文件、文件描述符、重定向以及为什么 Linux 下一切皆文件。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

【数据结构】5.1 树和二叉树的定义

前言 数形结构的关系是 1 对 n 的&#xff0c;树的每个元素后面都可以有多个后继&#xff0c;但是只能有 1 个前趋。 树形结构&#xff08;非线性结构&#xff09; 结点之间有分支具有层次关系 5.1.1 数的定义 树&#xff08;Tree&#xff09;是 n &#xff08;n > 0&…

java中javaSE与javaEE的区别

javaSE是什么&#xff1f; 怎么说吧&#xff0c;可以理解为javaSE是java的基石&#xff0c;如果将java程序想象成一座高楼大厦&#xff0c;那么javaSE就是地基。 官方的解释&#xff1a; Java SE&#xff08;Java Platform&#xff0c;Standard Edition&#xff09; Java SE 以…

实验室设计基本原则SICOLAB

实验室设计基本原则SICOLAB实验室设计、实验室建设施工SICOLAB实验室布局必须符合实验流程的规律&#xff0c;从样品接收、样品暂存、试剂和耗材储存、前处理和准备、样品分析测试、清洗到废物回收和处理&#xff0c;都必须有一个清晰的流程。要区分人流和物流&#xff0c;以及…

SQL DELETE 语句

DELETE 语句用于删除表中的记录。 SQL DELETE 语句 DELETE 语句用于删除表中的行。 SQL DELETE 语法 DELETE FROM table_name WHERE condition; 参数说明&#xff1a; table_name&#xff1a;要删除的表名称。condition&#xff1a;删除条件&#xff0c;用于指定哪些数据要…

小程序开发经验分享(4)-框架的选择

3个小程序开发框架 小程序开发公认的3个小程序开发框架: 原生、wepy、mpvue。3者个有利弊: 原生框架:微信的亲儿子,可直接在微信开发者工具中开发,方便调试,结构直接对应微信文档,框架无缝升级,最快支持最新版本的开发基础库。缺点是原生开发提供的开发方式比较朴素,…

2023年网络安全比赛--网络安全事件响应中职组(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.黑客通过网络攻入本地服务器,通过特殊手段在系统中建立了多个异常进程,找出启动异常进程的脚本,并将其绝对路径作为Flag值提交; 2.黑客通过网络攻入本地服务器,通过特殊手段在系统…

2022年12月国产数据库大事记-墨天轮

本文为墨天轮技术社区整理的2022年12月国产数据库大事件和重要产品发布消息。 目录 12月国产数据库大事记&#xff08;时间线&#xff09;产品/版本发布兼容认证排行榜新增数据库厂商活动相关资料 12月国产数据库大事记&#xff08;时间线&#xff09; 12月2日&#xff0c;…

linux环境tomcat发布系统

目录 1、上传需要发布的war 包 2、查看当前java线程 3、杀死需要启动的服务的java线程 3、修改上传的war包名称&#xff0c;删除原来的代码包 4、重新启动程序 5、查看启动日志 1、上传需要发布的war 包 通过XFTP上传war 包到 服务器 tomcat -->webapps 目录下。 注…

测试开发 | 相比 Selenium,Web 自动化测试框架 Playwright 有哪些强大的优势?

Playwright 是由微软的研发团队所开发的一款 Web 自动化测试框架&#xff0c;这个框架具有多平台、跨语言的特点。除了基本的自动化测试能力之外&#xff0c;同时它还具备非常强大的录制功能、追踪功能。以下是 Playwright 与 Selenium 的对比。 由此可见&#xff0c;Playwrigh…

Nginx编译安装vts监控模块

目录 1、环境准备 2、编译安装Nginx源码 2.1、安装依赖 2.2、编译安装Nginx 2.3验证Nginx安装 3、Nginx添加配置 3.11、Http模块下面添加 3.1.2、添加status访问页面 4、启动Nginx 4.3、访问前端页面 1、环境准备 参考Github vts模块参考Github nginx-1.20.2.tar.…

《Kotlin核心编程》笔记:val 和 var 字符串

Kotin重要特性&#xff1a;类型推导&#xff1a;定义变量不再需要显示声明类型&#xff0c;由编译器自动推导出变量的类型 如 val a "hello” val b 5 val c 10L表达式函数: fun sum(x: Int, y: Int) x y // 省略了{}&#xff0c;Kotlin支持这种用单行表达式与等号…

Git——git分支操作

1 什么是分支 在版本控制过程中&#xff0c;同时推进多个任务&#xff0c;为每个任务&#xff0c;我们就可以创建每个任务的单独 分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开来&#xff0c;开发自己分支的时 候&#xff0c;不会影响主线分支的运行。对于初…

ArcGIS基础实验操作100例--实验52导出点要素的坐标值

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验52 导出点要素的坐标值 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;…

flutter:如何实现局部导航管理?

引言 今天&#xff0c;小编给大家分享如何在 flutter 中实现 ‘局部导航’。开始之前我们先来统一一下关于 局部导航 的概念。 局部导航是什么&#xff1f; 我们在 flutter 中使用 navigator 来管理 app 的页面堆栈&#xff0c;主要包括 push、pop 这两种操作。而当我们UI设…

gitee实现项目托管

一、下载git官网链接&#xff1a;Git - Downloads安装参考:https://blog.csdn.net/weixin_44950987/article/details/102619708二、登录gitee&#xff08;码云&#xff09;&#xff0c;添加SSH公钥2.1、生成SSH公钥过程2.2、打开刚刚下载的git 然后跟着步骤来&#xff1a;2.3、…

C程序设计教程(02)—— 概述部分练习题

C程序设计教程&#xff08;02&#xff09;—— 概述部分练习题 一、填空题 1、结构化程序设计采用顺序结构、&#xff08;分支结构或选择结构&#xff09;和循环结构等三种基本结构编写程序。 2、程序由&#xff08;一个或多个函数&#xff09;组成&#xff0c;其中必须有且…

2.ISAAC 环境配置

ISAAC 环境配置 本文档介绍了如何开始使用 Isaac SDK 和 Isaac Sim 进行开发。 完成本文档中的步骤后&#xff0c;您应该准备好开始使用 Isaac SDK 开发机器人应用程序。 预安装 Isaac 目前仅支持 Ubuntu 18.04 LTS 从您的工作站进行开发和模拟。 请确保在您的工作站上安装最…

机器学习记录

概念辨析&#xff1a; 人工智能包含机器学习&#xff0c;机器学习包含深度学习 机器学习 机器学习约等于&#xff1a;looking for Function 深度学习&#xff1a;Function就是一个类神经网络 如果输出是一个数值就就叫回归 如果输出是几种类别就是分类 自监督学习为机器学习…

逻辑漏洞渗透与攻防(四)之任意账号注册

目录 任意账号注册 未验证邮箱/手机号 批量注册 个人信息伪造 前端验证审核绕过 邮箱/手机号注册激活验证绕过 用户名覆盖 任意账号注册 未验证邮箱/手机号 未验证邮箱/手机号&#xff0c;目前很多应用为了方便用户记录自己的用户名与密码&#xff0c;都可以使用邮箱…

【Spring6核心源码系列】IOC之BeanDefinition的封装

哎呀&#xff0c;又是午夜时分&#xff0c;又是一个失眠的夜晚&#xff0c;和去年一样&#xff0c;记得去年今日&#xff0c;也是睡不着觉&#xff0c;就注册了csdn的账号&#xff0c;开始写东西&#xff0c;csdn真是深夜最好的安魂剂。 Spring都发布了6.0&#xff0c;这不赶紧…