【Linux】基础IO—重定向与缓冲区

本文详细探讨了编程中的重定向技术、文件描述符的分配规则以及缓冲区的作用,包括输出重定向、追加重定向和使用缓冲区提高IO效率的方法。同时介绍了如何在自定义shell中实现文件描述符的管理和重定向功能。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、重定向

二、文件描述的分配规则

三、输出重定向和追加重定向

3.1、什么是缓冲区

3.2、为什么要有缓冲区

3.3、缓冲区如何使用的

3.3.1、刷新策略

3.3.2、特殊情况

3.4、我们来看一段代码

四、在自做的shell中加入重定向

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、重定向

const char* filename = "log.txt";
int main()
{
    struct stat st;
    int n = stat(filename, &st);// 获取一个文件对应的属性;失败,返回1
    if(n<0) return 1;

    printf("file size: %lu\n", st.st_size);// 文件大小是无符号整数

    //int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    int fd = open(filename, O_RDONLY);// 只读的方式打开文件
    if(fd < 0)
    {
        perror("open");
        return 2;
    }
    printf("fd: %d\n", fd);

    char *file_buffer = (char*)malloc(st.st_size + 1);

    n = read(fd, file_buffer, st.st_size);// 从文件描述符fd中读取,读取的内容放到file_buffer缓存区中,读取的内容有st.st_size大小
    // n>0:实际读取到了多少的字节;n=0:读到了文件的结尾
    if(n > 0)
    {
        file_buffer[n] = '\0';// 将有效字符的最后一位的下一个位置设置成'\0'
        printf("%s", file_buffer);
    }

    free(file_buffer);


    //const char *message = "hello Linux\n";
    //write(fd, message, strlen(message));
    //write(fd, message, strlen(message));
    //write(fd, message, strlen(message));
    //write(fd, message, strlen(message));

    close(fd);
    return 0;
}

二、文件描述的分配规则

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

const char* filename = "log.txt";

int main()
{

    //close(0);
    close(1);
    //close(2);
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

     //我们将文件描述符1关闭,那么我们的log.txt对应的文件描述符是1,
     //但是用printf和fprintf的时候,它们只认stdout,而stdout里面封装的文件描述符依旧是1;
     //可是在内核当中,1已经不是再指向当初的显示器了,而指向log.txt新打开的文件;
     //printf和fprintf默认是在显示器上打印的的,但是最终打印到log.txt文件当中了:这叫做重定向
    printf("printf, fd: %d\n", fd);
    fprintf(stdout, "fprintf, fd: %d\n", fd);

    fflush(stdout);


    close(fd);

    return 0;
}

文件描述符的分配规则:查自己的文件描述符表,分配最小的没有被使用的fd。

三、输出重定向和追加重定向

3.1、什么是缓冲区

缓冲区就是一段内存空间。

3.2、为什么要有缓冲区

给上层用户提供高效的IO体验,间接提高整体的效率。

如果没有语言级别的缓冲区的话,我们每次写入数据的时候,都要直接写到内核缓冲区的,那么就得需要操作系统的协助,但是OS是一个大忙人,它要管理的事情太多了,当你在内核文件缓冲区中写入数据的时候,而OS正在做其它的事情,那么你就要等到OS将事情做完之后,再来做你的事情,那么效率就会非常缓慢,所以结论是:调用系统调用,是有成本的,尽量少调用,效率就高了。

如果有了语言级别的缓冲区的话,我们只需要写入的数据写入语言级的缓冲区中,而且一次性可以写入多条数据,然后语言级的缓冲区再通过文件描述符将数据一次性的拷贝到内核文件缓冲区内,这样可以减少系统调用,效率自然就提高了。

3.3、缓冲区如何使用的

3.3.1、刷新策略

  1. 立即刷新。(无缓存)
  2. man fsync  2号手册
    int fsync(int fd);
    // 把数据立即从内核刷新到外设
  3. 行刷新。(显示器)照顾用户的查看习惯。
  4. 全缓冲。缓冲区写满,才刷新,普通文件。

3.3.2、特殊情况

  • 进程退出,系统会自动刷新。
  • 强制刷新。

3.4、我们来看一段代码

man dup2  //2号手册
int dup2(int oldfd,int newfd);
// 可以帮我们在底层做两个文件描述符对应的数组内容之间的值拷贝

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

const char* filename = "log.txt";

int main()
{

    // C
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    // system call
    const char* msg = "hello write\n";
    write(1, msg, strlen(msg));

    fork(); //???

    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    // O_TRUNC:清空文件+dup2()==输出重定向
    int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
    // O_APPEND:追加+dup2()==追加重定向
    int fd = open("/dev/pts/2", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    dup2(fd, 1);
    printf("hello world\n");
    fprintf(stdout, "hello world\n");
    fflush(stdout);

    close(fd);
    return 0;
}

每一个文件都有一个自己的缓冲区。

四、在自做的shell中加入重定向

进程的程序替换,会不会影响进程关联的或者打开的文件呢?

不会。 进程的程序替换,替换的是进程的代码和数据,修改的是地址空间和页表;内核的数据结构并不会受影响,和文件描述符表无关。

 #include <ctype.h>
 int isspace(int c);
// 判断是否是空格
:!man 2 open   
// 可以在我们的vim文件当中查找open的文档
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{\
    while(1){\
        if(isspace(cmd[pos]))\
            pos++;\
        else break;\
    }\
}while(0)

// "ls -a -l -n > myfile.txt"
#define None_Redir 0    // 没有重定向,设置为0
#define In_Redir   1    // 输入重定向,设置为1
#define Out_Redir  2    // 输出重定向,设置为2
#define App_Redir  3    // 追加重定向,设置为3

int redir_type = None_Redir;// 重定向的内容,默认设置为没有重定向
char* filename = NULL;// 为了获取重定向右边的文件名

// 为了方便,我就直接定义了
char cwd[SIZE * 2];
char* gArgv[NUM];
int lastcode = 0;

void Die()
{
    exit(1);
}

const char* GetHome()
{
    const char* home = getenv("HOME");
    if (home == NULL) return "/";
    return home;
}

const char* GetUserName()
{
    const char* name = getenv("USER");
    if (name == NULL) return "None";
    return name;
}
const char* GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    if (hostname == NULL) return "None";
    return hostname;
}
// 临时
const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if (cwd == NULL) return "None";
    return cwd;
}

// commandline : output
void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);
    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
    printf("%s", line);
    fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
    char* s = fgets(command, n, stdin);
    if (s == NULL) return -1;
    command[strlen(command) - 1] = ZERO;
    return strlen(command);
}


void SplitCommand(char command[], size_t n)
{
    (void)n;
    // "ls -a -l -n" -> "ls" "-a" "-l" "-n" 
    gArgv[0] = strtok(command, SEP);
    int index = 1;
    while ((gArgv[index++] = strtok(NULL, SEP))); // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
}

void ExecuteCommand()
{
    pid_t id = fork();
    if (id < 0) Die();
    else if (id == 0)
    {
        //重定向设置
        if (filename != NULL) {
            if (redir_type == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);// 标准输入流,将fd对应的地址拷贝到0对应的空间内
            }
            else if (redir_type == Out_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1);
            }
            else if (redir_type == App_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1);
            }
            else
            {
            }
        }

        // child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        // fahter
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            lastcode = WEXITSTATUS(status);
            if (lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
        }
    }
}

void Cd()
{
    const char* path = gArgv[1];
    if (path == NULL) path = GetHome();
    // path 一定存在
    chdir(path);

    // 刷新环境变量
    char temp[SIZE * 2];
    getcwd(temp, sizeof(temp));
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
    putenv(cwd); // OK
}

int CheckBuildin()
{
    int yes = 0;
    const char* enter_cmd = gArgv[0];
    if (strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;
        Cd();
    }
    else if (strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n", lastcode);
        lastcode = 0;
    }
    return yes;
}

// 检查是否有重定向
void CheckRedir(char cmd[])
{
    // > >> <
    // "ls -a -l -n >  myfile.txt"
    int pos = 0;// 定义的是下表的方式--->左闭右开
    int end = strlen(cmd);// 有效字符串的下一个位置

    while (pos < end)
    {
        if (cmd[pos] == '>')// '>'/'>>'
        {
            if (cmd[pos + 1] == '>')// 两个大于符号
            {
                cmd[pos++] = 0;
                pos++;
                redir_type = App_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
            else// 一个大于符号
            {
                cmd[pos++] = 0;// 让'<'变为0,起到分割字符串的作用
                redir_type = Out_Redir;// 重定向的类别
                SkipSpace(cmd, pos);// 遇到空格就跳过
                filename = cmd + pos;// 拿到右边的文件名
            }
        }
        else if (cmd[pos] == '<')
        {
            cmd[pos++] = 0;
            redir_type = In_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else
        {
            pos++;
        }
    }
}

int main()
{
    int quit = 0;
    while (!quit)
    {
        // 0. 重置--->重定向和文件名
        redir_type = None_Redir;
        filename = NULL;
        // 1. 我们需要自己输出一个命令行
        MakeCommandLineAndPrint();

        // 2. 获取用户命令字符串
        char usercommand[SIZE];
        int n = GetUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0) return 1;

        // 2.1 checkredir  检查是否有重定向
        CheckRedir(usercommand);

        // 2.2 debug
//        printf("cmd: %s\n", usercommand);
//        printf("redir: %d\n", redir_type);
//        printf("filename: %s\n", filename);
//
        // 3. 命令行字符串分割. 
        SplitCommand(usercommand, sizeof(usercommand));

        // 4. 检测命令是否是内建命令
        n = CheckBuildin();
        if (n) continue;
        // 5. 执行命令
        ExecuteCommand();
    }
    return 0;
}

总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值