基礎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ù)操作。
根據我們獲取到的當前進程的pid,再根據我們先前學到的知識,根據該pid在根目錄下的proc目錄下查看此進程的信息如下:
下面來解釋下cwd和exe:
- cwd表示當前進程所處的工作路徑。
- exe表示進程對應的可執(zhí)行程序的磁盤文件。
上面的運行結果也正如我們所料:file.txt創(chuàng)建在了與當前可執(zhí)行程序路徑所在的位置,也是當前進程所處的工作路徑,那是否就意味著這里說的“當前路徑”是指“當前可執(zhí)行程序所處的路徑”呢?還是說“當前路徑”是指“當前進程所處的路徑”呢?
- 這里我們把剛才生成的log.txt文件刪除掉,對代碼進行如下的修改,利用上次學到的chdir函數(shù)來更改此進程的工作路徑:chdir(“/home/wei”)
然后后再次運行myfile程序,結果如下:
此時現(xiàn)象已經很明顯了,我運行了file可執(zhí)行程序,但是并沒有在當前可執(zhí)行程序file所處在的lesson19目錄下看到我想要的file.txt文件,相反我卻在/home/wei路徑下看到了file.txt文件,這就足以證明我利用chdir更改進程所處的路徑后,生產的文件也隨之更改,這就證明此當前路徑即為當前進程所處的路徑,為了更具有說服力,我們依舊是利用proc查看當前進程9752的相關信息:
綜上,當前路徑就是當前進程所處的路徑?。。?/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程序:
- 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í)行此程序:
- 此時當我們以w方式打開文件,準備寫入的時候,其實文件已經先被清空了。
- fprintf向文件寫入時,換行符也是會被寫入到文件當中的
3、"a"追加:
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的功能:
問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:為什么要封裝?
- 原生系統(tǒng)接口,使用成本太高了!
- 使用原生系統(tǒng)接口,一段代碼只能在一個平臺上跑,不同的平臺暴露出的文件接口是不一樣的有時候,最終會導致語言不具備跨平臺性!
問4:封裝是如何解決跨平臺問題的呢?
- 窮舉所有的底層接口 + 條件編譯!
3.默認打開的三個流
- 都說 Linux 下一切皆文件,也就是說Linux下的任何東西都可以看作是文件,那么顯示器和鍵盤當然也可以看作是文件。我們能看到顯示器上的數(shù)據,是因為我們向“顯示器文件”寫入了數(shù)據,電腦能獲取到我們敲擊鍵盤時對應的字符,是因為電腦從“鍵盤文件”讀取了數(shù)據。
為什么我們向“顯示器文件“寫入數(shù)據以及從“鍵盤文件”讀取數(shù)據前,不需要進行打開“顯示器文件”和鍵盤文件“的相應操作?
- 需要注意的是,打開文件一定是進程運行的時候打開的,而任何進程在運行的時候都會默認打開三個輸入輸出流,即標準輸入流、標準輸出流、標準錯誤流,對應到C語言中就是stdin,stdout,stderr。其中,標準輸入流對應的設備就是鍵盤,標準輸出流和標準錯誤流對應的設備都是顯示器。
查看man手冊我們就可以發(fā)現(xiàn),stdin、stdout、stderr實際上都是FILE*類型的。
當我們的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ù)據,也就是顯示到顯示器上。
注意:不止是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平臺下運行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)當中都是以宏的方式進行定義的
- 例如,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;
}
下面來正式使用系統(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指令查看相關信息:
注意看這里輸出的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:
運行此程序,查看真實的權限值:
- 怎么實際的權限值為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,起始權限按位與后不會改變。
注意:創(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文件的內容:
如果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)的。
所以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)調用。
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;
}
注意:我們知道要讀取的內容是字符串,所以在數(shù)組buffer里面,需要手動設置字符串的末尾為\0,方便printf打印字符串。
0,‘\0’,NULL等字面值實際上都是0,只不過他們的類型不同。
5.對比C語言文件操作與系統(tǒng)的文件操作
1、C中以"w"方式打開文件,是會清空原文件的
- C語言的文件操作中,以w的方式打開文件時,是會清空原文件的,可我們使用系統(tǒng)接口對文件操作,按照如下的測試用例是不足以實現(xiàn)向C語言那樣清空原文件再寫入的功能:
- 為了實現(xiàn)向C語言那樣以w的方式寫入并且先清空源文件的內容再寫入新內容的操作,我們需要給open的第二個參數(shù)多家一個選項(O_TRUNC),此選項的作用就是清空原文件的內容。
- 對比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:
- 對比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。
我們可以嘗試一次打開多個文件,然后分別打印它們的文件描述符:
#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ù)且遞增的:
問1:為什么返回的文件描述符是從3開始,0,1,2去哪了呢?
其實這里的0、1、2被默認打開了,0對應的就是標準輸入(鍵盤),1對應標準輸出(顯示器),2對應標準錯誤(顯示器),標準輸入、標準輸出、標準錯誤輸出是系統(tǒng)默認打開的三個標準文件,系統(tǒng)定義的三個文件指針stdin、stdout、stderr中一定含有文件描述符,我們用如下代碼驗證:
而C語言我們學過三個文件流指針:
上述一開始的fd返回值是用的系統(tǒng)接口open函數(shù)的,而這里stdin,stdout,stderr對應的是C語言的,而C庫函數(shù)的內部調用的是系統(tǒng)接口,對應關系如下:
上述系統(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結構體,其內部包含了我想看到的文件的大部分內容 + 屬性,然后將這些結構體以雙鏈表的形式鏈接起來,隨后對被打開文件的管理,就轉換成為了對鏈表的增刪改查!
**問3:**進程和文件之間的對應關系是如何建立的?
- 我們知道,當一個程序運行起來時,操作系統(tǒng)會將該程序的代碼和數(shù)據加載到內存,然后為其創(chuàng)建對應的task_struct,mm_struct,頁表等相關的數(shù)據結構,并通過頁表建立虛擬內存和物理內存之間的關系:
- 而task_struct中還有一個指針(struct files_struct * files),該指針指向一個名為files_struct的結構體,在該結構體當中有一個名為fd_array的指針數(shù)組,里面存放的是struct file*的指針,該指針指向的就是被打開的文件結構,如果沒有指向,那就指向NULL。該數(shù)組的下標就是我們所謂的文件描述符。此時就把進程和文件的映射關系建立好了!
- 我們上述所畫的這一坨都是在內核中實現(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下一切皆文件”,也就是說未來你想打開一個文件,把讀寫方法和屬性記下來,在內核里給你這個硬件創(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開始進行分配。
若我們在打開這5個文件之前,先關閉文件描述符為0的文件,此時文件描述符的分配又會是怎樣的呢?
close(0);
可以看到,第一個打開的文件獲取到的文件描述符變成了0,而之后打開文件獲取到的文件描述符依舊是從3開始依次遞增的:
如果我先同時關閉文件描述符為0和2的文件呢,此時文件描述符的分配又會是怎樣的呢?
close(0);
close(2);
可以看到前兩個打開的文件獲取到的文件描述符是0和2,之后打開文件獲取到的文件描述符才是從3開始依次遞增的。
如果我先關閉文件描述符為1的文件呢,此時文件描述符的分配又會是怎樣的呢?
close(1);
結果竟然是空的:
在給出原因前,我們先總結文件描述符的分配規(guī)則:
- 從頭遍歷數(shù)組fd_array[ ],找到一個最小的,沒有被使用的下標,分配給新的文件。
而上述輸出結果為空的原因就是:printf是往stdout輸出的,stdout對應的就是1,根據fd的分配規(guī)則,fd就是1,雖然已經不再指向對應的顯示器了,但是已經指向了log1.txt的底層struct file對象!正常情況下結果應該出現(xiàn)在log1.txt文件里,可是我們卻并不能看到:
理論上來說輸出的值是在loga.txt文件里的,這里我們在close關閉文件之前指向下面的語句即可:
fflush(stdout);
至于這里為什么必須要加fflush,這就和我們之前提到過的緩沖區(qū)有關了,并且我printf應該是往顯示器上輸出內容的,卻直接輸出到了文件里,這就是重定向。具體詳情下文解釋。
六、重定向
1.重定向的原理
*1**、輸出重定向原理:*
- 輸出重定向就是,將我們本應該輸出到一個文件的數(shù)據重定向輸出到另一個文件中。
如上圖所示:
- 當我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;
}
注意:C語言的數(shù)據并不是立馬寫到了內存操作系統(tǒng)里頭,而是寫到了C語言的緩沖區(qū)當中,所以當使用printf打印完后需要使用fflush將C語言緩沖區(qū)當中的數(shù)據刷新到文件中
*2、追加重定向原理:*
- 追加重定向和輸出重定向的唯一區(qū)別就是,輸出重定向是覆蓋式輸出數(shù)據,而追加重定向是追加式輸出數(shù)據。
例如:我們想讓本應該輸出到“顯示器文件”的數(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;
}
3、輸入重定向<原理:
- 輸入重定向就是,將我們本應該從一個文件讀取數(shù)據,現(xiàn)在重定向為從另一個文件讀取數(shù)據。
例如,如果我們想讓本應該從“鍵盤文件”讀取數(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;
}
*問:標準輸出流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行字符串:
這樣看起來標準輸出流和標準錯誤流并沒有區(qū)別,都是向顯示器輸出數(shù)據。但我們若是將程序運行結果重定向輸出到文件log.txt當中,我們會發(fā)現(xiàn)log.txt文件當中只有向標準輸出流輸出的四行字符串,而向標準錯誤流輸出的兩行數(shù)據并沒有重定向到文件當中,而是仍然輸出到了顯示器上。
實際上我們使用重定向時,默認重定向的是文件描述符是1的標準輸出流,而并不會對文件描述符是2的標準錯誤流進行重定向。 如果我現(xiàn)在也想重定向文件描述符為2的標準錯誤流呢?看如下的指令:
./process stdout.txt 2>stderr.txt
上述指令做了兩次重定向,第一次把標準輸出重定向到了文件描述符為1的顯示器,第二次是把標準錯誤重定向到了文件描述符為2的顯示器,上述把標準輸出和標準錯誤區(qū)分開的意義是可以區(qū)分日常程序中哪些是輸出,哪些是錯誤,我們需要對錯誤信息做區(qū)分。
前面已經提到,重定向只會默認把標準輸出的進行處理,并不會重定向標準錯誤,如果我想讓標準輸出和標準錯誤一同混合起來到一個文件顯示,看如下指令:
./process all.txt 2&1
此時會發(fā)現(xiàn)此指令讓標準輸出和標準錯誤輸出到了同一個文件,畫圖解釋:
- 上述指令讓本來應該指向1的內容全部寫到all.txt文件,隨后把1里的內容拷貝到2里,再把2的內容寫到all.txt文件。
補充:perror
- perror內部套用了errno(全局變量),目的是記錄最近一次C庫函數(shù)調用失敗的原因。下面使用庫函數(shù)的perror和自己模擬實現(xiàn)的my_perror分別測試一次:
庫函數(shù)的perror:
自己實現(xiàn)的my_perror:
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的文件。
注意:
- 如果oldfd不是有效的文件描述符,則dup2調用失敗,并且此時文件描述符為newfd的文件沒有被關閉。
- 如果oldfd是一個有效的文件描述符,但是newfd和oldfd具有相同的值,則dup2不做任何操作,并返回newfd。
*補充:*
- 上述的拷貝是把舊的文件描述符下標oldfd的內容拷貝到新的文件描述符下標newfd,并非拷貝數(shù)字,最終只剩oldfd下標對應的內容。
示例1:輸出重定向
- 拋去上文的close(1),這里我們新打開的文件log.txt分配的文件描述符理應為3,如下圖所示:
我本來是向顯示器打印的,現(xiàn)在想輸出重定向到log.txt上, 也就是把log.txt的文件描述符3的內容拷貝到stdout文件描述符1里頭去。
代碼如下:
#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;
}
示例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;
}
示例3:輸入重定向<文章來源:http://www.zghlxwxcb.cn/news/detail-437662.html
- 輸入重定向就是把從鍵盤讀取數(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;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-437662.html
到了這里,關于<Linux> 基礎IO(文件操作、文件描述符fd、重定向)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!