天天看點

[Linux]----檔案操作(重定向+緩沖區)

文章目錄

  • ​​前言​​
  • ​​一、重定向​​
  • ​​具體操作​​
  • ​​dup2​​
  • ​​二、關于緩沖區的了解​​
  • ​​1. 什麼是緩沖區​​
  • ​​2. 為什麼要有緩沖區​​
  • ​​3. 緩沖區在哪裡​​
  • ​​重新整理政策機制​​
  • ​​如果在重新整理之前,關閉了fd會有什麼問題?​​
  • ​​FLIE内部的封裝​​
  • ​​特殊情況​​
  • ​​模拟實作封裝C标準庫​​
  • ​​标準輸出和标準錯誤​​
  • ​​總結​​

前言

本節繼續基于上節檔案描述符繼續往下拓展,講一講關于檔案操作的重定向和緩沖區。

​正文開始!​

首先來基于上節課的問題

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
    close(1);
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    fprintf(stdout,"打開檔案成功,fd=%d",fd);
    //fflush(stdout);
    close(fd);
}      
[Linux]----檔案操作(重定向+緩沖區)

我們發現既沒有往顯示器列印,也沒有往檔案裡面列印!

然後我們将fflush()這一行代碼取消注釋後

[Linux]----檔案操作(重定向+緩沖區)

我們可以發現列印内容到了檔案中了。至于為什麼要用fflush()函數,需要了解到緩沖區的内容,接下來帶大家了解!

那我有一個問題了?為什麼不往顯示器去列印,而是列印在了檔案中呢???

一、重定向

[Linux]----檔案操作(重定向+緩沖區)

如果我們要進行重定向,上層隻認0,1,2,3這樣的fd,我們可以在OS内部,通過一定的方式調整數組的特定下标内容夠(指向),我們就可以完成重定向操作!

對于上圖,我們進行重定向後,fprintf并不知道1号檔案描述符指向了"log.txt"檔案,而繼續向1号檔案描述符列印東西。

具體操作

上面的一堆的資料,都是核心資料結構。隻有誰有權限呢???

必定是作業系統(OS)---->必定提供系統結構!

dup2

[Linux]----檔案操作(重定向+緩沖區)

相比于dup,dup2更複雜一些,我們今天主要使用多duo2進行重定向操作!

[Linux]----檔案操作(重定向+緩沖區)

在這裡我們首先進行輸出重定向

stdout–>1 log.txt–>fd
  1. 那麼對于dup2()接口,誰是誰的一份拷貝呢?

    對于上面框起來的内容翻譯就是newfd是oldfd的一份拷貝,就是把oldfd的内容放置newfd裡面。最後隻剩oldfd了!!!

  2. 參數怎麼傳呢??

    我們要輸出重定向到檔案中,即就是stdout的輸出到檔案中,即就是1号檔案描述符的内容要指向新建立檔案的描述符。

dup2(fd,1);
int main()
{

    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    dup2(fd,1);
    fprintf(stdout,"打開檔案成功,fd=%d",fd);

    fflush(stdout);
    close(fd);
}      
[Linux]----檔案操作(重定向+緩沖區)

輸入重定向

int main()
{

    int fd=open("log.txt",O_RDONLY);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    dup2(fd,0);
    char line[64];
    while(fgets(line,sizeof(line),stdin)!=NULL)
    {
        printf(line);
    }

    fflush(stdout);
    close(fd);
}      
[Linux]----檔案操作(重定向+緩沖區)

二、關于緩沖區的了解

1. 什麼是緩沖區

  • 緩沖區的本質:就是一段記憶體。

2. 為什麼要有緩沖區

  • 解放使用緩沖區的程序時間
  • 緩沖區的存在可以集中處理資料重新整理,減少IO的次數,進而達到提高整機的效率的目的!

3. 緩沖區在哪裡

我來寫一份代碼帶大家驗證一下!

int main()
{
    printf("hello printf\n");   //stdout-->1
    const char* msg="hello write\n";
    write(1,msg,strlen(msg));
}      
[Linux]----檔案操作(重定向+緩沖區)

去掉’'以後

int main()
{
    printf("hello printf");   //stdout-->1
    const char* msg="hello write";
    write(1,msg,strlen(msg));
}      
[Linux]----檔案操作(重定向+緩沖區)

printf沒有立即重新整理的原因,是因為有緩沖區的存在

write可是立即重新整理的!

是以我們根據以上的實驗現象我們可以發現stdout必定封裝了write!

那麼這個緩沖區不在哪裡?? ---->一定不在wirte内部!

是以我們曾經讨論的緩沖區,不是核心級别的!

是以這個緩沖區在哪裡???—>隻能是C語言提供的!!!(語言級别的緩沖區)

因為printf是往stdout中列印,stdout—>FILE—>struct—>封裝很多的屬性---->fd—>該FILE對于的語言級别的緩沖區!

[Linux]----檔案操作(重定向+緩沖區)

重新整理政策機制

int main()
{
    printf("hello printf");//stdout->1->封裝了write
    fprintf(stdout,"hello fprintf");
    fputs("hello fputs",stdout);

    const char* msg="hello write";
    write(1,msg,strlen(msg));

    return 0;
}      
[Linux]----檔案操作(重定向+緩沖區)

什麼時候重新整理?

  • 無緩沖(立即重新整理)
  • 行緩沖(逐行重新整理)—>顯示器檔案
  • 全緩沖(緩沖區滿,重新整理)—>塊裝置對應的檔案(磁盤檔案)

無緩沖的特殊情況

a.程序退出

b.使用者強制重新整理

如果在重新整理之前,關閉了fd會有什麼問題?

int main()
{
    printf("hello printf");//stdout->1->封裝了write
    fprintf(stdout,"hello fprintf");
    fputs("hello fputs",stdout);

    const char* msg="hello write";
    write(1,msg,strlen(msg));
    close(1);
    //close(stdout->_fileno);//和上面close(1)效果相同
    return 0;
}      
[Linux]----檔案操作(重定向+緩沖區)

一開始我們隻是把資料寫入到FILE結構體的緩沖區,然後你把檔案描述符給關了,當然就寫不進檔案中了!!!

[Linux]----檔案操作(重定向+緩沖區)

現在我們就能來了解一開始我們抛出的問題了!!!

既然緩沖區在FILE内部,在C語言中,而我們每一次打開一個檔案,都要有一個FILE*會傳回!!

那就意味着,每一個檔案都有一個fd和屬于他自己的語言級别的緩沖區!!!

FLIE内部的封裝

在/usr/include/libio.h
struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 //緩沖區相關
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */      
[Linux]----檔案操作(重定向+緩沖區)

特殊情況

int main()
{
    const char* str1="hello printf\n";
    const char* str2="hello fprintf\n";
    const char* str3="hello fputs\n";
    const char* str4="hello write\n";
    
    //C庫函數
    printf(str1);
    fprintf(stdout,str2);
    fputs(str3,stdout);
    
    //系統接口
    write(1,str4,strlen(str4));
    
    //是調用完了上面的代碼,才執行的fork
    fork();
}      
[Linux]----檔案操作(重定向+緩沖區)

對代碼去掉’\n’

int main()
{
    const char* str1="hello printf";
    const char* str2="hello fprintf";
    const char* str3="hello fputs";
    const char* str4="hello write";
    
    //C庫函數
    printf(str1);
    fprintf(stdout,str2);
    fputs(str3,stdout);
    
    //系統接口
    write(1,str4,strlen(str4));
    
    //是調用完了上面的代碼,才執行的fork
    fork();
}      
[Linux]----檔案操作(重定向+緩沖區)
  1. 重新整理的本質,就是把緩沖區的資料寫到OS内部,清空緩沖區!
  2. 緩沖區是自己的FILE内部維護的,屬于父程序
  3. 子程序也繼承了父程序的緩沖區,也就列印了父程序緩沖區的内容!

模拟實作封裝C标準庫

隻封裝了C語言庫的一部分!

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

#define NUM 1024

#define NONE_FLUSH 0x0
#define LINE_FLUSH 0x1
#define FULL_FLUSH 0x2

typedef struct _MyFILE
{
    int _fileno;
    char buffer[NUM];
    int _end;
    int _flags;//fflush method
}MyFILE;

MyFILE* my_fopen(const char* filename,const char* method)
{
    assert(filename&&method);
    int flags=O_RDONLY;

    if(strcmp(method,"r")==0)
    {}
    else if(strcmp(method,"r+")==0)
    {} 
    else if(strcmp(method,"w")==0)
    {
        flags=O_WRONLY|O_CREAT|O_TRUNC;

    }
    else if(strcmp(method,"w+")==0)
    {}
    else if(strcmp(method,"a")==0)
    {
        flags=O_WRONLY|O_CREAT|O_APPEND;
    }
    else if(strcmp(method,"a+")==0)
    {}
    
    int fileno=open(filename,flags,0666);
    if(fileno<0)  return NULL;
    MyFILE* fp=(MyFILE*)malloc(sizeof(MyFILE));
    if(fp==NULL)
    {
        return NULL;
    }
    memset(fp,0,sizeof(MyFILE));
    fp->_fileno=fileno;
    fp->_flags|=LINE_FLUSH;
    fp->_end=0;
    return fp;
}

void my_fflush(MyFILE* fp)
{
    assert(fp);
    if(fp->_end>0)
    {
        write(fp->_fileno,fp->buffer,fp->_end);
        fp->_end=0;
        syncfs(fp->_fileno);
    }
    
}


void my_fwrite(MyFILE* fp,const char* start,int len)
{
    assert(fp&&start&&len>0);

    strncpy(fp->buffer+fp->_end,start,len);//将資料寫入到緩沖區了
    fp->_end+=len;

    if(fp->_flags&NONE_FLUSH)
    {

    }
    else if(fp->_flags&LINE_FLUSH)
    {
        if(fp->_end>0&&fp->buffer[fp->_end-1]=='\n')
        {
            //僅僅是寫入到核心中
            write(fp->_fileno,fp->buffer,fp->_end);
            fp->_end=0;
        }
    }
    else if(fp->_flags&FULL_FLUSH)
    {

    }
}

void my_close(MyFILE* fp)
{
    my_fflush(fp);
    close(fp->_fileno);
    free(fp);
}

int main()
{
    MyFILE* fp=my_fopen("log.txt","w");
    if(fp==NULL)
    {
        printf("my_fopen fail\n");
        return 1;
    }
    const char* s="hello my 111\n";
    my_fwrite(fp,s,strlen(s));
    
    printf("消息立即重新整理");
    sleep(3);


    const char* ss="hello my 222";
    my_fwrite(fp,ss,strlen(ss));
    sleep(3);
    printf("寫入了一個不滿足條件的字元串\n");


    const char* sss="hello my 333";
    my_fwrite(fp,sss,strlen(sss));
    sleep(3);
    printf("寫入了一個不滿足條件的字元串\n");


    const char* ssss="end\n";
    my_fwrite(fp,ssss,strlen(ssss));
    sleep(3);
    printf("寫入了一個滿足條件的字元串\n");

    const char* sssss="aaaaaaaaaa";
    my_fwrite(fp,sssss,strlen(sssss));
    printf("寫入了一個不滿足條件的字元串\n");
    
    sleep(1);
    my_fflush(fp);
    sleep(3);

    my_close(fp);
    return 0;
}      
[Linux]----檔案操作(重定向+緩沖區)

标準輸出和标準錯誤

#include<iostream>
#include<cstdio>

int main()
{
    //stdout
    printf("hello printf 1\n");
    fprintf(stdout,"hello fprintf 1\n");
    fputs("hello puts 1\n",stdout);

    //stderr
    fprintf(stderr,"hello fprintf 2\n");
    fputs("hello puts 2\n",stderr);
    perror("hello perror 2");

    //cout
    std::cout<<"hello cout 1"<<std::endl;

    //cerr
    std::cerr<<"hello cerr 2"<<std::endl;

}      
[Linux]----檔案操作(重定向+緩沖區)

我們發現打1的都不見了

[Linux]----檔案操作(重定向+緩沖區)
[Linux]----檔案操作(重定向+緩沖區)
[Linux]----檔案操作(重定向+緩沖區)

向stdout裡面打的都重定向到log.txt檔案中,stderr繼續列印到顯示器。

[Linux]----檔案操作(重定向+緩沖區)
./a.out >stdout.txt 2>stderr.txt

一條語句進行兩次重定向

那麼意義在哪裡呢?

可以區分那些是程式日常輸出,那些是錯誤!

我們也可以将上面的兩個檔案列印在一個檔案

./a.out >all.txt 2>&1

總結