国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

這篇具有很好參考價值的文章主要介紹了<Linux> 基礎IO(文件操作、文件描述符fd、重定向)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

基礎IO(文件操作、文件描述符fd、重定向)

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

一、回顧C和C++的文件操作

1、空文件也要在磁盤占用

  • 我們創(chuàng)建的文件,雖然里面并沒有存放數(shù)據,但是文件屬性也是數(shù)據,即便你創(chuàng)建一個空文件,也要占據磁盤空間

2、文件 = 文件內容 + 文件屬性

  • 文件內容就是真正寫入的內容,文件屬性就是文件名、文件大小、文件的權限、擁有者所屬組……

3、文件操作 = 文件內容的操作 + 文件屬性的操作 + 文件內容和屬性的操作

  • 我們有可能在操作文件的過程中,既改變內容,又改變屬性。文件操作的本質:進程和被打開文件的關系

4、一個文件如果沒有被打開,可以直接對文件進行訪問嗎?

  • 不能!一個文件要被訪問,就必須先被打開!

5、所謂的“打開”文件,究竟在干什么?

  • 將文件的屬性內容或內容加載到內存中,根據馮諾依曼體系結構決定的,cpu只能從內存中對數(shù)據做讀寫

6、是不是所有的文件,都會處于被打開的狀態(tài)?沒有被打開的文件,在哪里?

  • 不是的,沒有被打開的文件只在磁盤上靜靜的存儲著!

7、標定一個文件,必須使用:文件路徑 + 文件名(唯一性)

  • 我們要尋找或標定一個文件,必須使用文件名和文件路徑,因為每個文件的(文件名+文件路徑)都是唯一的,幫助我們快速確定文件的位置。如果沒有指明對應的文件路徑,默認是在當前路徑進行文件訪問

8、通常我們打開文件,訪問文件,關閉文件,是誰在進行相關操作?

  • 進程!C語言中我們訪問文件需要用到fopen、fclose、fread、fwrite等接口寫完之后,代碼編譯之后,形成二進制可執(zhí)行程序后,如果不運行,文件對應的操作不能被執(zhí)行。如果運行此文件程序,此時才會執(zhí)行對應的代碼,然后才是真正的對文件進行相關的操作,所以真正對文件操作的其實是進程。

二、C語言文件IO

1.什么是當前路徑?

根據我們前面的學習,當fopen以寫入的方式打開一個文件時,若文件不存在,則會自動在當前路徑創(chuàng)建該文件,那么這里的當前路徑是什么呢?以下面的代碼為測試用例:

#include<stdio.h>
#include<unistd.h>
int main()
{
   FILE* fp = fopen("flie.txt", "w");//寫入
   if (fp == NULL)
   {
   	perror("fopen");
   	return 1;
   }
   printf("mypid: %d\n", getpid());
   while (1)
   {
   	sleep(1);
   }
   const char* msg = "hello world";
   int cnt = 1;
   while (cnt < 20)
   {
   	fprintf(fp, "%s: %d\n", msg, cnt++);
   }
   fclose(fp);
   return 0;
}

此段代碼我fopen寫入的方式打開了file.txt文件,那么我在lesson19目錄下運行可執(zhí)行程序myfile,那么該可執(zhí)行程序創(chuàng)建的file.txt文件會出現(xiàn)在lesson19目錄下,此外上述代碼還獲取到了當前進程的pid,并且讓此進程一直循環(huán)下去,方便后續(xù)操作。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

根據我們獲取到的當前進程的pid,再根據我們先前學到的知識,根據該pid在根目錄下的proc目錄下查看此進程的信息如下:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

下面來解釋下cwd和exe:

  • cwd表示當前進程所處的工作路徑。
  • exe表示進程對應的可執(zhí)行程序的磁盤文件。

上面的運行結果也正如我們所料:file.txt創(chuàng)建在了與當前可執(zhí)行程序路徑所在的位置,也是當前進程所處的工作路徑,那是否就意味著這里說的“當前路徑”是指“當前可執(zhí)行程序所處的路徑”呢?還是說“當前路徑”是指“當前進程所處的路徑”呢?

  • 這里我們把剛才生成的log.txt文件刪除掉,對代碼進行如下的修改,利用上次學到的chdir函數(shù)來更改此進程的工作路徑:chdir(“/home/wei”)

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

然后后再次運行myfile程序,結果如下:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

此時現(xiàn)象已經很明顯了,我運行了file可執(zhí)行程序,但是并沒有在當前可執(zhí)行程序file所處在的lesson19目錄下看到我想要的file.txt文件,相反我卻在/home/wei路徑下看到了file.txt文件,這就足以證明我利用chdir更改進程所處的路徑后,生產的文件也隨之更改,這就證明此當前路徑即為當前進程所處的路徑,為了更具有說服力,我們依舊是利用proc查看當前進程9752的相關信息:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

綜上,當前路徑就是當前進程所處的路徑?。。?/p>


2.C語言文件接口匯總

C語言文件操作函數(shù)如下:

*文件操作函數(shù)* *功能*
fopen 打開文件
fclose 關閉文件
fputc 寫入一個字符
fgetc 讀取一個字符
fputs 寫入一個字符串
fgets 讀取一個字符串
fprintf 格式化寫入數(shù)據
fscanf 格式化讀取數(shù)據
fwrite 向二進制文件寫入數(shù)據
fread 從二進制文件讀取數(shù)據
fseek 設置文件指針的位置
ftell 計算當前文件指針相對于起始位置的偏移量
rewind 設置文件指針到文件的起始位置
ferror 判斷文件操作過程中是否發(fā)生錯誤
feof 判斷文件指針是否讀取到文件末尾

C語言的文件操作我之前已經詳細講解過,如果想了解上述諸多文件操作函數(shù)的使用方法,還請?zhí)D到如下博文:

  • C語言文件操作

下面對fopen再強調下:

//打開文件
FILE * fopen ( const char * filename, const char * mode );

fopen參數(shù)含義:

  • 參數(shù)1:文件名
  • 參數(shù)2:文件打開方式

打開方式如下:

文件使用方式 含義 如果指定文件不存在
“r”(只讀) 為了輸入數(shù)據,打開一個已經存在的文本文件 出錯
“w”(只寫) 為了輸出數(shù)據,打開一個文本文件 建立一個新的文件
“a”(追加) 向文本文件尾添加數(shù)據 建立一個新的文件
“rb”(只讀) 為了輸入數(shù)據,打開一個二進制文件 出錯
“wb”(只寫) 為了輸出數(shù)據,打開一個二進制文件 建立一個新的文件
“ab”(追加) 向一個二進制文件尾添加數(shù)據 出錯
“r+”(讀寫) 為了讀和寫,打開一個文本文件 出錯
“w+”(讀寫) 為了讀和寫,建立一個新的文件 建立一個新的文件
“a+”(讀寫) 打開一個文件,在文件尾進行讀寫 建立一個新的文件
“rb+”(讀寫) 為了讀和寫打開一個二進制文件 出錯
“wb+”(讀寫) 為了讀和寫,新建一個新的二進制文件 建立一個新的文件
“ab+”(讀寫) 打開一個二進制文件,在文件尾進行讀和寫 建立一個新的文件

接下來取其中部分進行演示:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#define FILE_NAME "log.txt"  
int main()  
{  
     FILE*fp = fopen(FILE_NAME,"a");  
     //FILE*fp = fopen(FILE_NAME,"w");                                                           
     //FILE*fp = fopen(FILE_NAME,"r");
     // r、w、r+(讀寫,文件不存在就出錯)、w+(讀寫,文件不存在就創(chuàng)建文件)、a(append,追加,只寫的形式打開文件)、a+(以可讀寫的方式打開文件)      
     if(fp==NULL)         
     {                     
         perror("fopen");  
         exit(1);         
     }                    
                          
     int cnt = 5;         
     while(cnt)                                                   
     {                                                              
         fprintf(fp,"%s:%d\n","hello Linux",cnt--);// 對文件進行寫入  
     }                    
     fclose(fp);                                                                                                                       
   //  char buffer[64];                                                       
   //  while(fgets(buffer,sizeof(buffer) - 1,fp) != NULL)// sizeof-1的原因是有給文本末尾留一個位置,讓fgets放入 null character  
   //  {                                                       
   //      buffer[strlen(buffer)-1]=0;//把換行符改成結束符     
   //      puts(buffer);  
   //  }                  
   //  fclose(fp);        
     return 0;            
 } 

1、"r"只讀:

  • 我們預先給log.txt文件輸入以下內容,然后運行myfile程序:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • fgets在讀取文件內容的時候,換行符會被認為是有效字符讀取到緩沖字符數(shù)組里面的,并且在每行讀取結束后,fgets會自動添加null character到緩沖字符數(shù)組的每個字符串末尾處。
  • puts在將字符串打印的時候,會自動在字符串末尾追加一個換行符。所以為了防止puts打印兩個換行符,在while循環(huán)里面將buffer數(shù)組里面的換行符改為null character。
  • fgets在讀取的時候,以讀取到num-1個字符,或換行符,或者文件結束符為止,以先發(fā)生者為準,這就是讀取一行的內容。所以如果想要讀取多行內容,就需要搞一個while循環(huán)。

2、"w"只寫:

  • 如果一個文件本就存在,那么以w的方式寫入會先清空你文件的內容,如果不存在那么就創(chuàng)建個新的,并且從文件的最開始寫入。
  • 我們在上述已經創(chuàng)建好log.txt文件的基礎上執(zhí)行此程序:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 此時當我們以w方式打開文件,準備寫入的時候,其實文件已經先被清空了。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • fprintf向文件寫入時,換行符也是會被寫入到文件當中的

3、"a"追加:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

4、根據上述對文件的讀取,我們現(xiàn)在寫個小功能(利用文件操作模擬實現(xiàn)cat命令):

  • 代碼如下:
#include<stdio.h>
#include<unistd.h>
//myfilel filename
int main(int argc, char* argv[])
{
   if (argc != 2)
   {
       printf("Usage: %s filename\n", argv[0]);
       return 1;
   }
   FILE* fp = fopen(argv[1], "r");
   if (fp == NULL)
   {
       perror("fopen");
       return 1;
   }
   char buffer[64];
   while (fgets(buffer, sizeof(buffer), fp) != NULL)
   {
       printf("%s", buffer);
   }
   fclose(fp);
   return 0;
}
  • 現(xiàn)在運行可執(zhí)行程序myfile + 要讀取的文件名即可完成cat的功能:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

問1:當我們想文件寫入的時候,最終是不是向磁盤寫入?

  • 是的,磁盤是硬件,只有OS操作系統(tǒng)有資格向硬件寫入,我們不能繞開操作系統(tǒng)對磁盤資源的訪問,換言之,所有的上層訪問文件的操作,都必須貫穿操作系統(tǒng),而操作系統(tǒng)不相信任何人,所以只能使用操作系統(tǒng)提供的相關系統(tǒng)調用來讓OS被上層使用。

問2:如何理解printf?我們怎么從來沒有見過?

  • 首先,printf的結果是顯示到顯示器上的,顯示器是硬件,其管理者只能是操作系統(tǒng),必須通過對應的調用接口來訪問顯示器,所以printf內部一定封裝了系統(tǒng)接口
  • 我們從來沒有見過這些系統(tǒng)調用接口是因為所有的語言都對系統(tǒng)接口做了封裝,所以看不到底層的系統(tǒng)調用接口的差別

問3:為什么要封裝?

  1. 原生系統(tǒng)接口,使用成本太高了!
  2. 使用原生系統(tǒng)接口,一段代碼只能在一個平臺上跑,不同的平臺暴露出的文件接口是不一樣的有時候,最終會導致語言不具備跨平臺性!

問4:封裝是如何解決跨平臺問題的呢?

  • 窮舉所有的底層接口 + 條件編譯!

3.默認打開的三個流

  • 都說 Linux 下一切皆文件,也就是說Linux下的任何東西都可以看作是文件,那么顯示器和鍵盤當然也可以看作是文件。我們能看到顯示器上的數(shù)據,是因為我們向“顯示器文件”寫入了數(shù)據,電腦能獲取到我們敲擊鍵盤時對應的字符,是因為電腦從“鍵盤文件”讀取了數(shù)據。

為什么我們向“顯示器文件“寫入數(shù)據以及從“鍵盤文件”讀取數(shù)據前,不需要進行打開“顯示器文件”和鍵盤文件“的相應操作?

  • 需要注意的是,打開文件一定是進程運行的時候打開的,而任何進程在運行的時候都會默認打開三個輸入輸出流,即標準輸入流、標準輸出流、標準錯誤流,對應到C語言中就是stdin,stdout,stderr。其中,標準輸入流對應的設備就是鍵盤,標準輸出流和標準錯誤流對應的設備都是顯示器。

查看man手冊我們就可以發(fā)現(xiàn),stdin、stdout、stderr實際上都是FILE*類型的。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

當我們的C程序被運行起來時,操作系統(tǒng)就會默認使用C語言的相關接口將這三個輸入輸出流打開,之后我們才能調用類似于scanf和printf之類的函數(shù)向鍵盤和顯示器進行相應的輸入輸出操作。

也就是說,stdin、stdout、stderr與我們打開某一文件時獲取到的文件指針是同一個概念,試想我們使用fputs函數(shù)時,將其第二個參數(shù)設置為stdout,此時fputs函數(shù)會被和將數(shù)據顯示到顯示器上呢?

#include<stdio.h>
int main()
{
   fputs("hello stdin\n", stdout);
   fputs("hello stdout\n", stdout);
   fputs("hello stderr\n", stdout);
   return 0;
}

答案是肯定的,此時我們相當于使用fputs函數(shù)向“顯示器文件”寫入數(shù)據,也就是顯示到顯示器上。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

注意:不止是C語言中有標準輸入流、標準輸出流和標準錯誤流,C++當中也有對應的cin,cout和cerr,其它所有語言都有類似的概念。實際上這種特性并不是某種語言所特有的,而是由操作系統(tǒng)所支持的。


三、系統(tǒng)文件IO

操作文件除了C語言接口、C++接口或是其他語言的接口外,操作系統(tǒng)也有一套系統(tǒng)接口來進行文件的訪問。相比于C庫函數(shù)或其他語言的庫函數(shù)而言,系統(tǒng)調用接口更貼近底層,實際上這些語言的庫函數(shù)都是對系統(tǒng)接口進行了封裝。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

我們在Linux平臺下運行C代碼時,C庫函數(shù)就是對Linux系統(tǒng)調用接口進行的封裝,在Windows平臺下運行C代碼時,C庫函數(shù)就是對Windows系統(tǒng)調用接口進行的封裝,這樣做使得語言有了跨平臺性,也方便進行二次開發(fā)。


1.open

下面基于系統(tǒng)接口來實現(xiàn)對文件的操作,C語言中我們要打開文件用的是fopen,但是系統(tǒng)接口中我們使用open函數(shù)打開文件。

函數(shù)名稱 open
函數(shù)功能 打開或者創(chuàng)建文件
頭文件 #include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
函數(shù)原型 int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
參數(shù) 見后面
返回值 文件打開成功,則會返回新的文件描述符(大于0的整數(shù));打開失敗就會返回-1
  • open的第一個參數(shù)(pathname)

第一個參數(shù)pathname代表著要打開或創(chuàng)建的目標文件名

  • 若pathname以路徑的方式給出,則當需要創(chuàng)建該文件時,就在pathname路徑下進行創(chuàng)建。
  • 若pathname以文件名的方式給出,則當需要創(chuàng)建該文件時,默認在當前路徑下進行創(chuàng)建。(注意當前路徑的含義)

  • open的第二個參數(shù)(flags)

第二個參數(shù)flags表示打開文件要傳遞的選項(打開文件的方式),其常用選項有如下幾個:

參數(shù)選項 含義
O_RDONLY 以只讀的方式打開文件
O_WRONLY 以只寫的方式打開文件
O_RDWR 以讀寫的方式打開文件
O_CREAT 若文件不存在,則創(chuàng)建它。需要使用mode選項,來指明新文件的訪問權限
O_APPEND 以追加的方式打開文件
O_TRUNC 把原有文件清空

補充1:

  • 實際上傳入flags的每一個選項在系統(tǒng)當中都是以宏的方式進行定義的

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 例如,O_RDONLY、O_WRONLY、O_RDWR、O_CREAT在系統(tǒng)中的宏定義如下:
#define O_RDONLY        00
#define O_WRONLY        01
#define O_RDWR          02
#define O_CREAT       0100
  • 這些宏定義選項的共同點就是,它們的二進制序列當中有且只有一個比特位是1(O_RDONLY選項的二進制序列為全0,表示O_RDONLY選項為默認選項),且為1的比特位是各不相同的,這樣一來,在open函數(shù)內部就可以通過使用“與”運算來判斷是否設置了某一選項。
int open(arg1, arg2, arg3){
   if (arg2&O_RDONLY){
   	//設置了O_RDONLY選項
   }
   if (arg2&O_WRONLY){
   	//設置了O_WRONLY選項
   }
   if (arg2&O_RDWR){
   	//設置了O_RDWR選項
   }
   if (arg2&O_CREAT){
   	//設置了O_CREAT選項
   }
   //...
}

補充2:

  • 打開文件時,可以傳入多個參數(shù)選項,當有多個參數(shù)選項傳入時,將這些選項用或 “ | ” 運算符隔開。

例如,若想以只寫的方式打開文件,但當目標文件不存在時自動創(chuàng)建文件,則第二個參數(shù)設置如下:

 O_WRONLY | O_CREAT

補充3

  • 且系統(tǒng)接口open的第二個參數(shù)flags是整型,有32比特位,若將一個比特位作為一個標志位,則理論上flags可以傳遞32種不同的標志位,也就是說系統(tǒng)傳遞標記位,是用位圖結構來進行傳遞的!
  • 綜上:每一個宏標記,一般只需要有一個比特位是1,并且和其它宏對應的值不能重疊。
  • 要想理解open的第二個參數(shù),則需要先理解如何使用比特位來傳遞選項,如果想讓函數(shù)實現(xiàn)多種功能的話,我們可以利用或運算將多個選項 “粘合” 到一起,從而讓一個接口同時實現(xiàn)多種不同的功能。利用的原理就是宏整數(shù)的32比特位中只有一個比特位是1,且不同的宏的1的位置是不重疊的,這樣就可以利用或運算來同時實現(xiàn)多個功能。

實例:(如下我自己設計的標記位,在內部做不同的事情)

#include <stdio.h>
#include <stdlib.h>

//每一個宏,對應的數(shù)值,只有一個比特位是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);
   printf("---------------------\n");
   return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

下面來正式使用系統(tǒng)的open打開文件,并使用flags選項來做演示:

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    // C語言中的w選項實際上底層需要調用這么多的選項O_WRONLY O_CREAT O_TRUNC 0666
    // C語言中的a選項需要將O_TRUNC替換為O_APPEND
   int fd = open("log.txt", O_WRONLY | O_CREAT);
   if (fd < 0)
   {
   	//打開失敗
   	perror("open");//輸出對應的錯誤碼
   	return 1;
   }
   printf("fd: %d\n", fd);
   close(fd);
}

我們運行此程序,并用ll指令查看相關信息:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

注意看這里輸出的fd(open函數(shù)的返回值)是3,具體為何是3,等我們講到文件描述符再細說。我們確實把一個不存在的文件(log.txt)創(chuàng)建好了,可是此文件的權限都是亂碼,原因在于你新建一個文件,此文件要受linux權限約束的,這個新建文件的權限必須得告知操作系統(tǒng),所以當我們打開一個曾經并不存在的文件,我們不能用兩個參數(shù)的open,而是要用三個參數(shù)的open函數(shù),看下文講解:


  • open的第三個參數(shù)(mode)

第三個參數(shù)mode表示創(chuàng)建文件的默認權限。

  • 例如就上述的例子,我們把log.txt文件的mode默認權限設置為0666:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

運行此程序,查看真實的權限值:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 怎么實際的權限值為0664呢?實際上創(chuàng)建出來的權限值還會收到umask(文件默認權限掩碼)的影響,實際創(chuàng)建出來文件的權限值為:mode & (~umask)。umask的默認權限值一般為0002,當我們設置mode值為0666時實際創(chuàng)建出來的文件權限為0664。

若想創(chuàng)建出文件的權限值不受umask的影響,則需要在創(chuàng)建文件前使用umask函數(shù)將文件掩碼設置為0。這樣起始權限實際上就是最終文件的權限了,因為umask按位取反后是全1,起始權限按位與后不會改變。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

注意:創(chuàng)建目錄的命令mkdir,目錄起始權限默認是777,創(chuàng)建文件的命令touch,文件起始權限是0666,這些命令的實現(xiàn)實際上是要調用系統(tǒng)接口open的,并且在創(chuàng)建文件或目錄的時候要在open的第三個參數(shù)中設置文件的起始權限。


2.close

C語言關閉文件使用的是fclose,系統(tǒng)接口使用close函數(shù)關閉文件,close函數(shù)的原型如下:

函數(shù)名稱 close
函數(shù)功能 關閉文件
頭文件 #include <unistd.h>
函數(shù)原型 int close(int fd);
參數(shù) 文件描述符fd
返回值 文件關閉成功,則會返回0;關閉失敗就會返回-1,并且錯誤碼會被設置

使用close函數(shù)時傳入需要關閉文件的文件描述符即可,若關閉文件成功則返回0,關閉文件失敗則返回-1。

注意:在執(zhí)行完文件的操作后,要進行“關閉文件”操作。雖然程序在結束前會自動關閉所有的打開文件,但文件打開過多會導致系統(tǒng)運行緩慢,這時就要自行手動關閉不再使用的文件,來提高系統(tǒng)整體的執(zhí)行效率。


3.write

在C語言中我們對文件的寫入使用的是fprintf、fputs、fwrite……。在系統(tǒng)接口中,我們使用的是write函數(shù)向文件寫入信息,write函數(shù)的原型如下:

函數(shù)名稱 write
函數(shù)功能 打開或者創(chuàng)建文件
頭文件 #include <unistd.h>
函數(shù)原型 ssize_t write(int fd, const void *buf, size_t count);
參數(shù) fd:特定的文件描述符
buf:寫入緩沖區(qū)對應的字符串起始地址
count:寫入緩沖區(qū)的大小
返回值 寫入成功返回寫入的字節(jié)數(shù)(零表示未寫入任何內容),寫入失敗返回-1

示例:

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FILE_NAME "log.txt"

int main()                                  
{                                           
    umask(0);//將進程的umask值設置為0000    
                                             
    // C語言中的w選項實際上底層需要調用這么多的選項O_WRONLY O_CREAT O_TRUNC 0666
    // C語言中的a選項需要將O_TRUNC替換為O_APPEND
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT,0666);//設置文件起始權限為0666
    if(fd < 0)                              
    {                                       
       perror("open");                     
       return 1;//退出碼設置為1            
    }                                       
    close(fd);                              
    int cnt = 5;                            
    char outbuffer[64];                     
    while(cnt)                              
    {                                       
        sprintf(outbuffer,"%s:%d\n","hello linux",cnt--);
        //以\0作為字符串的結尾,是C語言的規(guī)定,和文件沒什么關系,文件要的是字符串的有效內容,不要\0
        //除非你就想把\0寫到文件里面取,否則strlen()不要+1
        write(fd,outbuffer,strlen(outbuffer));
    }                                       
    printf("fd:%d\n",fd);// 文件描述符的值為3                                                                   
    close(fd);
}

運行此程序,并用cat指令輸出寫入到log.txt文件的內容:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

如果write寫入時第三個參數(shù)要多加一個\0的位置,創(chuàng)建出來的log.txt用vim打開時會出現(xiàn)亂碼,以\0作為字符串的結束標志,這是C語言的規(guī)定和文件沒有關系,文件只要存儲有效內容就好了,不需要\0,所以在write寫入的時候,strlen求長度不要+1。

只將寫入的內容改為aaaa,打印出來的log.txt的內容就發(fā)生了覆蓋式寫入的現(xiàn)象,而不是先將文件原有內容清理,然后在重新寫入。
在C語言中,如果再次以寫的方式打開文件,會自動將原先文件中的內容清理掉,重新向文件寫入內容。
自動清空原有數(shù)據,實際上是通過open系統(tǒng)調用中的第三個宏參數(shù)O_TRUNC來實現(xiàn)的。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

所以C語言中打開文件時,使用的打開方式為w,在底層的open接口中,要用三個宏參數(shù)O_WRONLY,O_CREAT,O_TRUNC來實現(xiàn)。
C語言中的a打開方式,在系統(tǒng)底層實現(xiàn)上只需要將O_TRUNC替換為O_APPEND即可。
可見庫函數(shù)和系統(tǒng)調用的關系,本質就是庫函數(shù)封裝系統(tǒng)調用。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

4.read

C語言的讀取操作是fread,系統(tǒng)接口使用的是read函數(shù)從文件讀取信息,read函數(shù)的原型如下:

函數(shù)名稱 read
函數(shù)功能 打開或者創(chuàng)建文件
頭文件 #include <unistd.h>
函數(shù)原型 ssize_t write(int fd, void *buf, size_t count);
參數(shù) fd:特定的文件描述符
buf:寫入緩沖區(qū)對應的字符串起始地址
count:寫入緩沖區(qū)的大小
返回值 讀取成功返回實際讀取數(shù)據的字節(jié)數(shù)(零表示未寫入任何內容),數(shù)據讀取失敗返回-1

示例:

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
	umask(0);//將文件權限掩碼設置為0
 	// C語言中的w選項實際上底層需要調用這么多的選項O_WRONLY O_CREAT O_TRUNC 0666
 	// C語言中的a選項需要將O_TRUNC替換為O_APPEND
	int fd = open("log.txt", O_RDONLY, 0666);
	if (fd < 0)
	{
		//打開失敗
		perror("open");//輸出對應的錯誤碼
		return 1;
    }
 
	char buffer[1024];
	ssize_t num = read(fd, buffer, sizeof(buffer) - 1);
    //-1是因為我們寫入文件的時候并不需要\0,我們讀的時候手動添加\0,將其看做字符串
  	//讀出來,每個子字符串都是以\n結尾的,我們直接將整個字符串給讀出來
    //num>0說明我們讀到了有效的內容
    if(num > 0) buffer[num]=0;//字符數(shù)組中字面值0就是\0
    printf("%s",buffer);     
    close(fd);
	return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

注意:我們知道要讀取的內容是字符串,所以在數(shù)組buffer里面,需要手動設置字符串的末尾為\0,方便printf打印字符串。
0,‘\0’,NULL等字面值實際上都是0,只不過他們的類型不同。


5.對比C語言文件操作與系統(tǒng)的文件操作

1、C中以"w"方式打開文件,是會清空原文件的

  • C語言的文件操作中,以w的方式打開文件時,是會清空原文件的,可我們使用系統(tǒng)接口對文件操作,按照如下的測試用例是不足以實現(xiàn)向C語言那樣清空原文件再寫入的功能:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 為了實現(xiàn)向C語言那樣以w的方式寫入并且先清空源文件的內容再寫入新內容的操作,我們需要給open的第二個參數(shù)多家一個選項(O_TRUNC),此選項的作用就是清空原文件的內容。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 對比C語言完成上述功能和系統(tǒng)操作完成上述功能:
C語言操作:fopen("log.txt", "w");
系統(tǒng)操作:int id = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  • 綜上:實際上C語言以"w"的寫入方式底層的open調所傳入的選項就是O_WRONLY | O_CREAT | O_TRUNC。C語言只需要用到"w"即可了,但是系統(tǒng)方面就要傳這么多選項,而且屬性也要設置。

2、C中以"a"方式打開文件,是會追加的

  • C中以"a"的方式打開文件,是以追加的方式向文本文件尾部添加數(shù)據,為了讓我們的系統(tǒng)接口也能完成此追加操作,我們需要將open的第二個參數(shù)中的選項O_TRUNC換成O_APPEND:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 對比C語言完成上述功能和系統(tǒng)操作完成上述功能:
 C語言實現(xiàn):fopen("log.txt", "a");
系統(tǒng)操作:int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
  • 綜上:C語言中只用了一個"a"實現(xiàn)的追加功能,在系統(tǒng)底層,需要用到O_WRONLY | O_CREAT | O_APPEND這一組選項來完成對應的操作。

總結:實際上我們系統(tǒng)層面上的接口是我們C語言操作文件的底層實現(xiàn)。


6.open的返回值

open的返回值類型是int,如果打開文件成功,則返回新打開的文件描述符,若打開失敗,則返回-1。<Linux> 基礎IO(文件操作、文件描述符fd、重定向)
我們可以嘗試一次打開多個文件,然后分別打印它們的文件描述符:

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

#define FILE_NAME(number) "log.txt"#number

int main()
{
   umask(0);//將文件權限掩碼設置為0
   int fd0 = open(FLIE_NAME(1), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd1 = open(FLIE_NAME(2), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd2 = open(FLIE_NAME(3), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd3 = open(FLIE_NAME(4), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd4 = open(FLIE_NAME(5), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   printf("fda: %d\n", fd0);
   printf("fdb: %d\n", fd1);
   printf("fdc: %d\n", fd2);
   printf("fdd: %d\n", fd3);
   printf("fde: %d\n", fd4);
   close(fd0);
   close(fd1); 
   close(fd2); 
   close(fd3); 
   close(fd4);
   return 0;
}

運行程序后可以看到,打開文件的文件描述符是從3開始連續(xù)且遞增的:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

問1:為什么返回的文件描述符是從3開始,0,1,2去哪了呢?

其實這里的0、1、2被默認打開了,0對應的就是標準輸入(鍵盤),1對應標準輸出(顯示器),2對應標準錯誤(顯示器),標準輸入、標準輸出、標準錯誤輸出是系統(tǒng)默認打開的三個標準文件,系統(tǒng)定義的三個文件指針stdin、stdout、stderr中一定含有文件描述符,我們用如下代碼驗證:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

而C語言我們學過三個文件流指針:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

上述一開始的fd返回值是用的系統(tǒng)接口open函數(shù)的,而這里stdin,stdout,stderr對應的是C語言的,而C庫函數(shù)的內部調用的是系統(tǒng)接口,對應關系如下:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

上述系統(tǒng)接口的文件描述符和C語言中的文件指針到底是何關系,我們得先搞清楚FILE*的含義:

FILE*是文件指針,F(xiàn)ILE是C庫提供的結構體,內部封裝了多個成員,對文件操作而言,系統(tǒng)接口只認文件描述符fd,F(xiàn)ILE內部必定封裝了fd(_fileno就是文件指針內部結構體封裝的文件描述符)

再回到一開始的問題:為什么返回的文件描述符是從3開始,0,1,2去哪了呢?

  • 答案:因為Linux進程默認打開3個文件描述符,分別是標準輸入0,標準輸出1,標準錯誤2,既然0,1,2被占了,所以這就是為什么成功打開文件時所得到的文件描述符是從3開始進行分配的。

    問2:為什么返回值文件描述符fd會是0,1,2,3,4,5……,其它的不可以嗎?

  • 實際上這里所謂的文件描述符本質上是一個指針數(shù)組的下標,且這個下標對應的是內核的下標。我們上述調用open,write,read返回的文件描述符都是系統(tǒng)接口,都是操作系統(tǒng)提供的返回值,具體怎么個數(shù)組下標還請看下文的文件描述符fd。


四、文件描述符fd

  • 文件是由進程運行時打開的,一個進程可以打開多個文件,所以在內核中,進程 : 打開的文件 = 1 : n,而系統(tǒng)中又存在大量進程,也就是說,在系統(tǒng)中任何時刻都可能存在大量已經打開的文件。
  • OS操作系統(tǒng)需要對這些被打開的文件進行管理,管理方式就是先描述,再組織。所以一個文件被打開,在內核中,要創(chuàng)建被打開的文件的內核數(shù)據結構(先描述),即struct file結構體,其內部包含了我想看到的文件的大部分內容 + 屬性,然后將這些結構體以雙鏈表的形式鏈接起來,隨后對被打開文件的管理,就轉換成為了對鏈表的增刪改查!

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

**問3:**進程和文件之間的對應關系是如何建立的?

  • 我們知道,當一個程序運行起來時,操作系統(tǒng)會將該程序的代碼和數(shù)據加載到內存,然后為其創(chuàng)建對應的task_struct,mm_struct,頁表等相關的數(shù)據結構,并通過頁表建立虛擬內存和物理內存之間的關系:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 而task_struct中還有一個指針(struct files_struct * files),該指針指向一個名為files_struct的結構體,在該結構體當中有一個名為fd_array的指針數(shù)組,里面存放的是struct file*的指針,該指針指向的就是被打開的文件結構,如果沒有指向,那就指向NULL。該數(shù)組的下標就是我們所謂的文件描述符。此時就把進程和文件的映射關系建立好了!

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 我們上述所畫的這一坨都是在內核中實現(xiàn)的,因此,我們只要有某一文件的文件描述符,就可以找到與該文件相關的文件信息,從而對文件進行一系列輸入輸出操作。

再來解決上述一開始問的問題:為什么返回值文件描述符fd會是0,1,2,3,4,5……,其它的不可以嗎?

  • 答案:本質是因為文件描述符是一個指針數(shù)組的下標,系統(tǒng)當中使用指針數(shù)組的方式建立進程和文件之間的對應關系,將文件描述符返回給上層用戶,上層用戶就可以在調用后續(xù)接口繼續(xù)傳入文件描述符,來索引對應的指針數(shù)組來找到對應的文件。

問4:如何理解Linux下一切皆文件?

  • 如果我們要用C語言來實現(xiàn)面向對象(類),只能使用struct結構體,我們采用函數(shù)指針的形式來在struct中實現(xiàn)函數(shù)方法:
struct file
{
   //對象的屬性
   //函數(shù)指針
   void (*readp)(struct file *filep, int fd ...);
   void (*writep)(struct file *filep, int fd ...);
   ...
}
void read(struct file *filep, int fd ...)
{
   //邏輯代碼
}
void write(struct file *filep, int fd ...)
{
   //邏輯代碼
}
  • 在計算機里有各種硬件(鍵盤、顯示器、磁盤、網卡……),這些設備統(tǒng)一稱為外設(IO設備),以磁盤為例,它們都一定有對應自己的一套讀寫方法,不同的設備對應的讀寫方法一定是不一樣的,如果現(xiàn)在要打開磁盤,那么OS就在內核給你創(chuàng)建一套struct file,用readp指針指向read方法,writep指針指向write方法,打開顯示器等其它外設也是類似的,這一操作就是OS內的文件系統(tǒng)做的軟件封裝,再往上就是一個進程里的指針指向一結構體,該結構體內部有一個指針數(shù)組,下標就是文件描述符,其內部存放struct file*的指針,從而指向各個設備的讀或寫的方法。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 上述整個過程就是“Linux下一切皆文件”,也就是說未來你想打開一個文件,把讀寫方法和屬性記下來,在內核里給你這個硬件創(chuàng)建對應的struct file,初始化時把對應的函數(shù)指針指向你具體的設備,但在內核中存在的永遠都是struct file,用鏈表結構關聯(lián)起來,所以一個進程都以統(tǒng)一的視角看待文件,所以我們訪問不同的file指向的誰完全取決于其底層的讀寫方法。有點多態(tài)的感覺了。我們把上述的設計出的struct file來表示一個一個文件的叫做VFS虛擬文件系統(tǒng)。

問5:0,1,2對應stdin,stdout,stderr,對應的設備分別是鍵盤,顯示器,顯示器??蛇@些都是硬件啊,也用你上面的struct file來標識對應的文件嗎?

  • 其實此問題的答案在問4(Linux下一切皆文件)已經講解過了,當你打開一個文件,把讀寫方法和屬性記下來,在內核里給你這個硬件創(chuàng)建對應的struct file,初始化時把對應的函數(shù)指針指向你具體的設備,但在內核中存在的永遠都是struct file,用鏈表結構關聯(lián)起來,所以一個進程都以統(tǒng)一的視角看待文件,所以我們訪問不同的file指向的誰完全取決于其底層的讀寫方法。當然需要struct file來標識對應的文件。

五、文件描述符的分配規(guī)則

再次連續(xù)打開5個文件,看看這5個文件打開后獲取到的文件描述符:

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

#define FILE_NAME(number) "log.txt"#number

int main()
{
   umask(0);//將文件權限掩碼設置為0
   int fd0 = open(FLIE_NAME(1), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd1 = open(FLIE_NAME(2), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd2 = open(FLIE_NAME(3), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd3 = open(FLIE_NAME(4), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   int fd4 = open(FLIE_NAME(5), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   printf("fd: %d\n", fd0);
   printf("fd: %d\n", fd1);
   printf("fd: %d\n", fd2);
   printf("fd: %d\n", fd3);
   printf("fd: %d\n", fd4);
   close(fd0);
   close(fd1); 
   close(fd2); 
   close(fd3); 
   close(fd4);
   return 0;
}

可以看到這五個文件獲取到的文件描述符都是從3開始連續(xù)遞增的,這很好理解,因為文件描述符本質就是數(shù)組的下標,而當進程創(chuàng)建時就默認打開了標準輸入流、標準輸出流和標準錯誤流,也就是說數(shù)組當中下標為0、1、2的位置已經被占用了,所以只能從3開始進行分配。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

若我們在打開這5個文件之前,先關閉文件描述符為0的文件,此時文件描述符的分配又會是怎樣的呢?

close(0);

可以看到,第一個打開的文件獲取到的文件描述符變成了0,而之后打開文件獲取到的文件描述符依舊是從3開始依次遞增的:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

如果我先同時關閉文件描述符為0和2的文件呢,此時文件描述符的分配又會是怎樣的呢?

close(0);
close(2);

可以看到前兩個打開的文件獲取到的文件描述符是0和2,之后打開文件獲取到的文件描述符才是從3開始依次遞增的。
<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

如果我先關閉文件描述符為1的文件呢,此時文件描述符的分配又會是怎樣的呢?

close(1);

結果竟然是空的:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

在給出原因前,我們先總結文件描述符的分配規(guī)則:

  • 從頭遍歷數(shù)組fd_array[ ],找到一個最小的,沒有被使用的下標,分配給新的文件。

而上述輸出結果為空的原因就是:printf是往stdout輸出的,stdout對應的就是1,根據fd的分配規(guī)則,fd就是1,雖然已經不再指向對應的顯示器了,但是已經指向了log1.txt的底層struct file對象!正常情況下結果應該出現(xiàn)在log1.txt文件里,可是我們卻并不能看到:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

理論上來說輸出的值是在loga.txt文件里的,這里我們在close關閉文件之前指向下面的語句即可:

fflush(stdout);

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

至于這里為什么必須要加fflush,這就和我們之前提到過的緩沖區(qū)有關了,并且我printf應該是往顯示器上輸出內容的,卻直接輸出到了文件里,這就是重定向。具體詳情下文解釋。


六、重定向

1.重定向的原理

*1**、輸出重定向原理:*

  • 輸出重定向就是,將我們本應該輸出到一個文件的數(shù)據重定向輸出到另一個文件中。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

如上圖所示:

  • 當我close(1)后,指針數(shù)組下標1的位置就不再指向標準輸出流文件了,此時使用open函數(shù)打開一個log.txt文件, 根據文件描述符的分配規(guī)則:從頭遍歷數(shù)組fd_array[ ],找到一個最小的并且沒有被使用的下標,分配給新的文件。找到的1就分配給了log.txt文件,于是乎log.txt的地址就填到1里頭了,并把1返回給用戶。
  • 但是我標準庫里頭有個stdout,就是FILE,F(xiàn)ILE內部又封裝了fd,這個fd天然就是1,上述使用的fprintf就是向stdout打印,我上面所作的內容對應stdout來說是不知道的,它只知道要向1里寫入,但其實已經被貍貓換太子了,自然數(shù)據就寫入上文已經調整后的log.txt新文件了。

綜上,我們要進行重定向,上層只認0,1,2,3,4,5這樣的fd,我們可以在OS內部,通過一定的方式調整數(shù)組的特定下標的內容(指向),我們就可以完成重定向操作了,

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
   close(1);
   int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
   if (fd < 0)
   {
   	perror("open");
   	return 0;
   }
   //本來應該向顯示器打印,最終卻變成了向指定文件打印
   fprintf(stdout, "open fd: %d\n", fd);
   fflush(stdout);
   close(fd);
   return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

注意:C語言的數(shù)據并不是立馬寫到了內存操作系統(tǒng)里頭,而是寫到了C語言的緩沖區(qū)當中,所以當使用printf打印完后需要使用fflush將C語言緩沖區(qū)當中的數(shù)據刷新到文件中

*2、追加重定向原理:*

  • 追加重定向和輸出重定向的唯一區(qū)別就是,輸出重定向是覆蓋式輸出數(shù)據,而追加重定向是追加式輸出數(shù)據。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

例如:我們想讓本應該輸出到“顯示器文件”的數(shù)據追加式輸出到log.txt文件當中,那么我們應該先將文件描述符為1的文件關閉,然后再以追加式寫入的方式打開log.txt文件,這樣一來,我們就將數(shù)據追加重定向到了文件log.txt當中。

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
   close(1);
   int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
   if (fd < 0)
   {
   	perror("open");
   	return 0;
   }
   //本來應該向顯示器打印,最終卻變成了向指定文件打印
   fprintf(stdout, "open fd: %d\n", fd);
   fflush(stdout);
   close(fd);
   return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

3、輸入重定向<原理:

  • 輸入重定向就是,將我們本應該從一個文件讀取數(shù)據,現(xiàn)在重定向為從另一個文件讀取數(shù)據。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

例如,如果我們想讓本應該從“鍵盤文件”讀取數(shù)據的scanf函數(shù),改從log.txt文件中讀取數(shù)據,那么我們可以打開log.txt文件之前將文件描述符為0的文件關閉,也就是將“鍵盤文件”關閉,這樣一來,當我們后續(xù)打開log.txt文件時所分配到的文件描述符就是0.

#include<stdio.h>  
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
   close(0);
   int fd = open("log.txt", O_RDONLY);
   if (fd < 0)
   {
   	perror("open");
   	return 0;
   }
   char line[64];
   while (1)
   {
   	printf("<");
       if(fgets(line,sizeof(line),stdin)==NULL) break;                                                             printf("%s",line); 
   }
   fflush(stdout);
   close(fd);
   return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

*問:標準輸出流1和標準錯誤流2對應的都是顯示器,它們有什么區(qū)別?*

來看如下的代碼,代碼中分別向標準輸出流和標準錯誤流輸出了幾行字符串:

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
   //stdout
   printf("hello printf 1\n");
   fprintf(stdout, "hello fprintf 1\n");
   fputs("hello fputs 1\n", stdout);
   //stderr
   fprintf(stderr, "hello fprintf 2\n");
   fputs("hello puts 2\n", stderr);
   perror("hello perror 2"); //stderr                                                                                                                                               
   //cout 
   cout << "hello cout 1" << endl;
   //cerr
   cerr << "hello cerr 2" << endl;
   return 0;
}

直接運行程序,結果很顯然就是在顯示器上輸出8行字符串:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

這樣看起來標準輸出流和標準錯誤流并沒有區(qū)別,都是向顯示器輸出數(shù)據。但我們若是將程序運行結果重定向輸出到文件log.txt當中,我們會發(fā)現(xiàn)log.txt文件當中只有向標準輸出流輸出的四行字符串,而向標準錯誤流輸出的兩行數(shù)據并沒有重定向到文件當中,而是仍然輸出到了顯示器上。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

實際上我們使用重定向時,默認重定向的是文件描述符是1的標準輸出流,而并不會對文件描述符是2的標準錯誤流進行重定向。 如果我現(xiàn)在也想重定向文件描述符為2的標準錯誤流呢?看如下的指令:

./process  stdout.txt 2>stderr.txt

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

上述指令做了兩次重定向,第一次把標準輸出重定向到了文件描述符為1的顯示器,第二次是把標準錯誤重定向到了文件描述符為2的顯示器,上述把標準輸出和標準錯誤區(qū)分開的意義是可以區(qū)分日常程序中哪些是輸出,哪些是錯誤,我們需要對錯誤信息做區(qū)分。

前面已經提到,重定向只會默認把標準輸出的進行處理,并不會重定向標準錯誤,如果我想讓標準輸出和標準錯誤一同混合起來到一個文件顯示,看如下指令:

./process  all.txt 2&1

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

此時會發(fā)現(xiàn)此指令讓標準輸出和標準錯誤輸出到了同一個文件,畫圖解釋:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

  • 上述指令讓本來應該指向1的內容全部寫到all.txt文件,隨后把1里的內容拷貝到2里,再把2的內容寫到all.txt文件。

補充:perror

  • perror內部套用了errno(全局變量),目的是記錄最近一次C庫函數(shù)調用失敗的原因。下面使用庫函數(shù)的perror和自己模擬實現(xiàn)的my_perror分別測試一次:

庫函數(shù)的perror:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

自己實現(xiàn)的my_perror:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)


2.dup2

在Linux操作系統(tǒng)中提供了系統(tǒng)接口dup2,我們可以使用該函數(shù)完成重定向。dup2的函數(shù)原型如下:

函數(shù)名稱 dup2
函數(shù)功能 復制一個文件描述符
頭文件 #include<unistd.h>
函數(shù)原型 int dup2(int oldfd,int newfd);
參數(shù) oldfd:被復制的文件描述符 newfd:新的文件描述符
返回值 >-1:復制成功,返回新的文件描述符 -1:出錯

**函數(shù)功能:**dup2會將fd_array[oldfd]的內容拷貝到fd_array[newold]當中,如果有必要的話我們需要先使用關閉文件描述符為newfd的文件。

注意:

  1. 如果oldfd不是有效的文件描述符,則dup2調用失敗,并且此時文件描述符為newfd的文件沒有被關閉。
  2. 如果oldfd是一個有效的文件描述符,但是newfd和oldfd具有相同的值,則dup2不做任何操作,并返回newfd。

*補充:*

  • 上述的拷貝是把舊的文件描述符下標oldfd的內容拷貝到新的文件描述符下標newfd,并非拷貝數(shù)字,最終只剩oldfd下標對應的內容。

示例1:輸出重定向

  • 拋去上文的close(1),這里我們新打開的文件log.txt分配的文件描述符理應為3,如下圖所示:

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

我本來是向顯示器打印的,現(xiàn)在想輸出重定向到log.txt上, 也就是把log.txt的文件描述符3的內容拷貝到stdout文件描述符1里頭去。

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

代碼如下:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
	int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	if (fd < 0)
	{
		perror("open");
		return 0;
	}
	int ret = dup2(fd, 1);
	if (ret > 0)  close(fd);//關閉舊的
	printf("ret: %d\n", ret);//1
	//本來應該向顯示器打印,最終卻變成了向指定文件打印
	fprintf(stdout, "open fd: %d\n", fd);
	fflush(stdout);
	close(fd);
	return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

示例2:追加重定向

  • 追加重定向僅僅是在打開文件的方式發(fā)生了改變,由原先的O_TRUNC變成了O_APPEND,其它的和輸出重定向完全一樣:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
   int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
   if (fd < 0)
   {
   	perror("open");
   	return 0;
   }
   int ret = dup2(fd, 1);
   if (ret > 0)  close(fd);//關閉舊的
   printf("ret: %d\n", ret);//1
   //本來應該向顯示器打印,最終卻變成了向指定文件打印
   fprintf(stdout, "open fd: %d\n", fd);
   fflush(stdout);
   close(fd);
   return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)

示例3:輸入重定向<

  • 輸入重定向就是把從鍵盤讀取數(shù)據改為重定向從文件讀取數(shù)據,如下代碼示例:
#include<stdio.h>  
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
   int fd = open("log.txt", O_RDONLY);
   if (fd < 0)
   {
   	perror("open");
   	return 0;
   }
   dup2(fd, 0);
   char line[64];
   while (fgets(line, sizeof line, stdin) != NULL)
   {
   	printf("%s\n", line);
   }
   fflush(stdout);
   close(fd);
   return 0;
}

<Linux> 基礎IO(文件操作、文件描述符fd、重定向)文章來源地址http://www.zghlxwxcb.cn/news/detail-437662.html

到了這里,關于<Linux> 基礎IO(文件操作、文件描述符fd、重定向)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【Linux】基礎IO_文件描述符與重定向

    【Linux】基礎IO_文件描述符與重定向

    環(huán)境:centos7.6,騰訊云服務器 Linux文章都放在了專欄:【 Linux 】歡迎支持訂閱 相關文章推薦: 【Linux】馮.諾依曼體系結構與操作系統(tǒng) 【C/進階】如何對文件進行讀寫(含二進制)操作? 【Linux】基礎IO_文件操作 在前文中學習了open函數(shù),我們知道 open函數(shù)的返回值就是文件描

    2024年02月03日
    瀏覽(20)
  • [Linux]基礎IO詳解(系統(tǒng)文件I/O接口、文件描述符、理解重定向)

    [Linux]基礎IO詳解(系統(tǒng)文件I/O接口、文件描述符、理解重定向)

    ? ? ? ? hello,大家好,這里是bang___bang_ ,今天和大家談談Linux中的基礎IO,包含內容有對應的系統(tǒng)文件I/O接口,文件描述符,理解重定向。 ?? 目錄 1??初識文件 2?? 系統(tǒng)文件I/O接口 ??open ??write ??read ??close 3??文件描述符 ??012 ??內核中文件描述符的探究 ??分配

    2024年02月12日
    瀏覽(20)
  • 【探索Linux】—— 強大的命令行工具 P.12(文件描述符 | 重定向 | 基礎IO)

    【探索Linux】—— 強大的命令行工具 P.12(文件描述符 | 重定向 | 基礎IO)

    前面我們講了C語言的基礎知識,也了解了一些數(shù)據結構,并且講了有關C++的一些知識,也學習了一些Linux的基本操作,也了解并學習了有關Linux開發(fā)工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代碼也相信大家都掌握的不錯,上一篇文章我們了解了基礎IO,文件操作,今天

    2024年02月08日
    瀏覽(30)
  • 【探索Linux】文件描述符 | 重定向 | 基礎IO —— 強大的命令行工具 P.12

    【探索Linux】文件描述符 | 重定向 | 基礎IO —— 強大的命令行工具 P.12

    前面我們講了C語言的基礎知識,也了解了一些數(shù)據結構,并且講了有關C++的一些知識,也學習了一些Linux的基本操作,也了解并學習了有關Linux開發(fā)工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代碼也相信大家都掌握的不錯,上一篇文章我們了解了基礎IO,文件操作,今天

    2024年02月08日
    瀏覽(25)
  • 【Linux】文件描述符與重定向操作

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

    收錄于【Linux】文件系統(tǒng)?專欄 對于Linux下文件的寫入與讀取,以及文件原理還有疑惑的可以看看上一篇文章淺談文件原理與操作。 目錄 系列文章 再談文件描述符 IO函數(shù)的本質 一切皆文件 文件重定向 原理 系統(tǒng)接口 ??上一篇文章中,我們就提到了 open 的返回值即 fd ,又稱

    2024年02月09日
    瀏覽(28)
  • 【Linux】文件描述符 - fd

    【Linux】文件描述符 - fd

    使用 man open 指令查看手冊: open 函數(shù)具體使用哪個,和具體應用場景有關。如:目標文件不存在,需要 open 創(chuàng)建,則第三個參數(shù)表示創(chuàng)建文件的默認權限;否則使用兩個參數(shù)的 open。 write read close lseek ,類比 C 文件相關接口。 操作文件,除了使用 C 語言的接口【Linux】回顧

    2024年03月23日
    瀏覽(24)
  • 【Linux】基礎 IO(文件描述符)-- 詳解

    【Linux】基礎 IO(文件描述符)-- 詳解

    1、 文件的宏觀理解 文件在哪呢? 從廣義上理解,鍵盤、顯示器、網卡、聲卡、顯卡、磁盤等幾乎所有的外設都可以稱之為文件,因為 “Linux 下,一切皆文件”。 從狹義上的理解, 文件在 磁盤(硬件) 上放著 ,只有操作系統(tǒng)才能真正的去訪問磁盤。磁盤是一種永久存儲介

    2024年03月24日
    瀏覽(22)
  • Linux--文件描述符fd的本質

    Linux--文件描述符fd的本質

    ?? ?

    2024年02月16日
    瀏覽(21)
  • Linux - fd文件描述符和文件詳解

    Linux - fd文件描述符和文件詳解

    ? ???????????????????????????????????????????????????????? ? ? ? ? ? ????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???感謝各位 點贊 收藏 評論 三連支持 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 本文

    2024年02月08日
    瀏覽(20)
  • 【linux深入剖析】文件描述符 | 對比 fd 和 FILE | 緩沖區(qū)

    【linux深入剖析】文件描述符 | 對比 fd 和 FILE | 緩沖區(qū)

    ??你好,我是 RO-BERRY ?? 致力于C、C++、數(shù)據結構、TCP/IP、數(shù)據庫等等一系列知識 ??感謝你的陪伴與支持 ,故事既有了開頭,就要畫上一個完美的句號,讓我們一起加油 通過對open函數(shù)的學習,我們知道了文件描述符就是一個小整數(shù) 而現(xiàn)在知道,文件描述符就是從0開始的小

    2024年04月13日
    瀏覽(31)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包