文件操作
打開(kāi)文件
1.使用open()函數(shù)打開(kāi)和創(chuàng)建文件
- 手冊(cè)文件 man 2 open
函數(shù)頭文件及函數(shù)原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函數(shù)參數(shù):
pathname:待打開(kāi)文件的絕對(duì)路徑和文件名。
flags:打開(kāi)的旗標(biāo)類型,或稱模式,
O_RDONLY 只讀模式打開(kāi)文件
O_WRONLY 只寫(xiě)模式打開(kāi)文件
O_RDWR 讀寫(xiě)模式打開(kāi)文件
O_CREAT 若欲打開(kāi)的文件不存在則自動(dòng)建立該文件
O_TRUNC 若文件存在并且以可寫(xiě)的方式打開(kāi)時(shí), 此旗標(biāo)會(huì)令文件長(zhǎng)度清為0,
而原來(lái)存于該文件的資料也會(huì)消失。
O_EXCL 如果O_CREAT 也被設(shè)置, 此指令會(huì)去檢查文件是否存在。
文件若不存在則建立該文件,否則將導(dǎo)致打開(kāi)文件錯(cuò)誤.
此外, 若O_CREAT 與O_EXCL 同時(shí)設(shè)置,并且欲打開(kāi)的文件為符號(hào)連接,
則會(huì)打開(kāi)文件失敗。
參數(shù)mode僅在flags中含有O_CREAT時(shí)有效,設(shè)定新建文文件的打開(kāi)權(quán)限,有下列數(shù)種組合,
S_IRWXU 00700 權(quán)限,代表該文件所有者具有可讀、可寫(xiě)及可執(zhí)行的權(quán)限。
S_IRUSR 或S_IREAD, 00400 權(quán)限,代表該文件所有者具有可讀取的權(quán)限。
S_IWUSR 或S_IWRITE, 00200 權(quán)限,代表該文件所有者具有可寫(xiě)入的權(quán)限。
S_IXUSR 或S_IEXEC, 00100 權(quán)限,代表該文件所有者具有可執(zhí)行的權(quán)限。
S_IRWXG 00070 權(quán)限,代表該文件用戶組具有可讀、可寫(xiě)及可執(zhí)行的權(quán)限。
S_IRGRP 00040 權(quán)限,代表該文件用戶組具有可讀的權(quán)限。
S_IWGRP 00020 權(quán)限,代表該文件用戶組具有可寫(xiě)入的權(quán)限。
S_IXGRP 00010 權(quán)限,代表該文件用戶組具有可執(zhí)行的權(quán)限。
S_IRWXO 00007 權(quán)限,代表其他用戶具有可讀、可寫(xiě)及可執(zhí)行的權(quán)限。
S_IROTH 00004 權(quán)限,代表其他用戶具有可讀的權(quán)限。
S_IWOTH 00002 權(quán)限,代表其他用戶具有可寫(xiě)入的權(quán)限。
S_IXOTH 00001 權(quán)限,xit代表其他用戶具有可執(zhí)行的權(quán)限。
函數(shù)返回值: 打開(kāi)文件成功,返回一個(gè)文件描述符 >2;打開(kāi)失敗,返回-1。
提示:使用 access()作用戶認(rèn)證方面的判斷要特別小心, 例如在access()后再作open()空文件可能會(huì)造成系統(tǒng)安全上的問(wèn)題。
2.使用create()函數(shù)創(chuàng)建并打開(kāi)文件
函數(shù)原型
int creat(const char *pathname, mode_t mode);
相當(dāng)于使用調(diào)用方式,
open(const char *pathname, (O_CREAT|O_WRONLY|O_TRUNC));
函數(shù)參數(shù):
pathname 待打開(kāi)文件的絕對(duì)路徑和文件名。
mode 新創(chuàng)建文件的權(quán)限,見(jiàn)上面open()
函數(shù)返回值:若成功會(huì)返回新的文件描述符,若有錯(cuò)誤發(fā)生則會(huì)返回-1。
提示:creat()無(wú)法建立特別的裝置文件,如果需要請(qǐng)使用mknod()。
讀寫(xiě)文件
1.使用read()函數(shù)從文件中讀取數(shù)據(jù)
- 手冊(cè)文件 man 2 read
函數(shù)頭文件及函數(shù)原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
函數(shù)參數(shù):
fd 文件指針,提供數(shù)據(jù)的文件的文件描述符,讀取的數(shù)據(jù)的來(lái)源。
buf 讀到的數(shù)據(jù)所存放的內(nèi)存空間的起始地址,同時(shí)文件的當(dāng)前讀寫(xiě)位置向后移。
count 想要讀取的數(shù)據(jù)的字節(jié)數(shù),也是提供的存儲(chǔ)空間字節(jié)數(shù)。
函數(shù)說(shuō)明及返回值: read()會(huì)把參數(shù)fd 所指的文件傳送count 個(gè)字節(jié)到buf 指針?biāo)傅膬?nèi)存中(暨在[0,count]區(qū)間變化)。
1.若參數(shù)count 為0,則read()不會(huì)有作用并返回0。
2.成功時(shí),返回值為實(shí)際讀取到的字節(jié)數(shù)。
3.如果返回0,表示已到達(dá)文件尾,暨碰到了EOF或是無(wú)可讀取的數(shù)據(jù)。
4.此外文件讀寫(xiě)位置會(huì)隨讀取到的字節(jié)移動(dòng)。
5.有錯(cuò)誤發(fā)生時(shí)則返回-1,而文件讀寫(xiě)位置則無(wú)法預(yù)測(cè)。
提示:
read()函數(shù)負(fù)責(zé)從文件句柄中讀取指定數(shù)量的字節(jié),并將這些字節(jié)放在標(biāo)量型變量中。read()函數(shù)和標(biāo)準(zhǔn)I/O函數(shù)fread()相同的方式處理I/O緩沖的。為了提高效率,read()函數(shù)并不是一次讀取一個(gè)字節(jié),而是讀取一塊數(shù)據(jù)并保存到臨時(shí)存儲(chǔ)區(qū)中。然后,C的fread函數(shù)與Perl的read函數(shù)會(huì)從臨時(shí)緩沖區(qū)將數(shù)據(jù)一次一個(gè)字節(jié)地傳送給程序。print()函數(shù)(而不是write()函數(shù)負(fù)責(zé)輸出read()函數(shù)返回的實(shí)際字節(jié)。print()函數(shù)類似于C中的fwrite()函數(shù)。
附加:如果順利 read()會(huì)返回實(shí)際讀到的字節(jié)數(shù),最好能將返回值與參數(shù)count 作比較,若返回的字節(jié)數(shù)比要求讀取的字節(jié)數(shù)少,則
1. 讀取普通文件時(shí),讀到文件末尾還不夠 nbytes 字節(jié)。例如:如果文件只有 30 字節(jié),
而我們想讀取 100字節(jié),那么實(shí)際讀到的只有 30 字節(jié),read 函數(shù)返回 30 。
此時(shí)再使用 read 函數(shù)作用于這個(gè)文件會(huì)導(dǎo)致 read 返回 0 。
2. 從終端設(shè)備(terminal device)讀取時(shí),一般情況下每次只能讀取一行。
3. 從網(wǎng)絡(luò)讀取時(shí),網(wǎng)絡(luò)緩存可能導(dǎo)致讀取的字節(jié)數(shù)小于 nbytes 字節(jié)。
4. 讀取 pipe 或者 FIFO 時(shí),pipe 或 FIFO 里的字節(jié)數(shù)可能小于 nbytes 。
5. 從面向記錄的設(shè)備讀取時(shí),某些面向記錄的設(shè)備(如磁帶)每次最多只能返回一個(gè)記錄。
6. 在讀取了部分?jǐn)?shù)據(jù)時(shí)被信號(hào)中斷。讀操作始于 cfo 。在成功返回之前,cfo 增加,
增量為實(shí)際讀取到的字節(jié)數(shù)。
2.使用write()函數(shù)向指定文件中寫(xiě)入數(shù)據(jù)
- 手冊(cè)文件 man 2 write
函數(shù)頭文件及函數(shù)原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
函數(shù)參數(shù):
fd 待寫(xiě)入數(shù)據(jù)的文件的描述符
buf 寫(xiě)入數(shù)據(jù)的起始地址
count 待寫(xiě)入的數(shù)據(jù)的字節(jié)數(shù)
函數(shù)說(shuō)明及返回值: write()會(huì)把參數(shù)buf 所指的內(nèi)存寫(xiě)入count 個(gè)字節(jié)到參數(shù)fd 所指的文件內(nèi)。當(dāng)然,文件讀寫(xiě)位置也會(huì)隨之移動(dòng)。
如果順利會(huì)返回實(shí)際寫(xiě)入數(shù)據(jù)的字節(jié)數(shù),表示寫(xiě)了部分或者全部的數(shù)據(jù)。
當(dāng)有錯(cuò)誤發(fā)生時(shí),返回-1,我們要根據(jù)錯(cuò)誤的類型來(lái)處理。如果錯(cuò)誤為EINTR表示在寫(xiě)時(shí)出現(xiàn)了中斷錯(cuò)誤。如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問(wèn)題。
提示:對(duì)于普通文件,寫(xiě)操作始于 cfo 。如果打開(kāi)文件時(shí)使用了 O_APPEND,則每次寫(xiě)操作都將數(shù)據(jù)寫(xiě)入文件末尾。成功寫(xiě)入后,cfo 增加,增量為實(shí)際寫(xiě)入的字節(jié)數(shù)。
定位文件
預(yù)概念: 所有打開(kāi)的文件都有一個(gè)當(dāng)前文件偏移量(current file offset),以下簡(jiǎn)稱為 cfo。cfo 通常是一個(gè)非負(fù)整數(shù),用于表明文件開(kāi)始處到文件當(dāng)前位置的字節(jié)數(shù)。讀寫(xiě)操作通常開(kāi)始于 cfo,并且使 cfo 增大,增量為讀寫(xiě)的字節(jié)數(shù)。文件被打開(kāi)時(shí),cfo 會(huì)被初始化為 0,除非使用了 O_APPEND 。
使用lseek()函數(shù)定位指定已打開(kāi)文件的讀寫(xiě)指針
- 手冊(cè)文件 man lseek
函數(shù)頭文件及函數(shù)原型
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函數(shù)參數(shù):
fd 待重新定位讀寫(xiě)指針位置的文件的描述符
offset 讀寫(xiě)指針的偏移量(可正可負(fù)可為0)
whence 讀寫(xiě)指針的偏移位置
SEEK_SET 相對(duì)文件首部偏移,文件偏移量將被設(shè)置為 offset。
SEEK_CUR 相對(duì)文件當(dāng)前讀寫(xiě)位置偏移,文件偏移量將被設(shè)置為 cfo 加上 offset,
offset 可以為正也可以為負(fù)。
SEEK_END 相對(duì)文件尾部偏移,文件偏移量將被設(shè)置為文件長(zhǎng)度加上 offset,
offset 可以為正也可以為負(fù)。
函數(shù)說(shuō)明及返回值: 每一個(gè)已打開(kāi)的文件都有一個(gè)讀寫(xiě)位置,當(dāng)打開(kāi)文件時(shí)通常其讀寫(xiě)位置是指向文件開(kāi)頭,若是以附加的方式打開(kāi)文件(如O_APPEND),則讀寫(xiě)位置會(huì)指向文件尾。當(dāng)read()或write()時(shí),讀寫(xiě)位置會(huì)隨之增加,lseek()便是用來(lái)控制該文件的讀寫(xiě)位置。參數(shù)fildes 為已打開(kāi)的文件描述詞,參數(shù)offset 為根據(jù)參數(shù)whence來(lái)移動(dòng)讀寫(xiě)位置的位移數(shù)。當(dāng)調(diào)用成功時(shí)則返回目前的讀寫(xiě)位置,也就是距離文件多少個(gè)字節(jié)數(shù)。若有錯(cuò)誤則返回-1。
例:
將讀寫(xiě)位置移到文件開(kāi)頭時(shí): lseek(int fildes, 0, SEEK_SET);
將讀寫(xiě)位置移到文件尾時(shí): lseek(int fildes, 0, SEEK_END);
想要取得目前文件位置時(shí): lseek(int fildes, 0, SEEK_CUR);
提示:
1.Linux 系統(tǒng)不允許lseek()對(duì)tty 裝置作用,此項(xiàng)動(dòng)作會(huì)令lseek()返回ESPIPE。
2.如果參數(shù) fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 為 ESPIPE。 對(duì)于普通文件(regular file),cfo 是一個(gè)非負(fù)整數(shù)。但對(duì)于特殊設(shè)備,cfo 有可能是負(fù)數(shù)。因此,我們不能簡(jiǎn)單地測(cè)試 lseek 的返回值是否小于 0 來(lái)判斷 lseek 成功與否,而應(yīng)該測(cè)試 lseek 的返回值是否等于 -1 來(lái)判斷 lseek 成功與否。
3.lseek 僅將 cfo 保存于內(nèi)核中,不會(huì)導(dǎo)致任何 I/O 操作。這個(gè) cfo 將被用于之后的讀寫(xiě)操作。
4.如果 offset 比文件的當(dāng)前長(zhǎng)度更大,下一個(gè)寫(xiě)操作就會(huì)把文件“撐大(extend)”。這就是所謂的在文件里創(chuàng)造"空洞(hole)”。沒(méi)有被實(shí)際寫(xiě)入文件的所有字節(jié)由重復(fù)的 0 表示。空洞是否占用硬盤空間是由文件系統(tǒng)(file system)決定的。
關(guān)閉文件
使用close函數(shù)關(guān)閉指定文件
- 手冊(cè)文件 man close
函數(shù)頭文件及函數(shù)原型
#include <unistd.h>
int close(int fd);
函數(shù)參數(shù):
fd 為open()或creat()打開(kāi)的文件描述符。
函數(shù)說(shuō)明及返回值: 當(dāng)使用完已打開(kāi)的文件后若已不再需要?jiǎng)t可使用 close()關(guān)閉該文件, 而close()會(huì)讓數(shù)據(jù)寫(xiě)回磁盤, 并釋放該文件所占用的資源. 參數(shù)fd 為先前由open()或creat()所返回的文件描述詞.**返回值:若文件順利關(guān)閉則返回0, 發(fā)生錯(cuò)誤時(shí)返回-1.
提示:雖然在進(jìn)程結(jié)束時(shí),系統(tǒng)會(huì)自動(dòng)關(guān)閉已打開(kāi)的文件,但仍建議自行關(guān)閉文件,并確實(shí)檢查返回值。
綜合案例
// ./my-cp <src_file> <dst_file>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFFER_SIZE 100
int main(int argc, char *argv[])
{
if(argc != 3)
{
printf("usage : %s <src_file> <dst_file>\n",
argv[0]);
return 1;
}
int src_fd = 0;
int dst_fd = 0;
int n = 0;
char buf[BUFFER_SIZE] = {'\0'};
char *src_file = argv[1];
char *dst_file = argv[2];
// 1.open
// 1.1 以只讀方式打開(kāi)源文件
if((src_fd = open(src_file, O_RDONLY)) == -1)
{
perror("open src error");
return 1;
}
// 1.2 以只寫(xiě)方式打開(kāi)目的文件
if((dst_fd = open(dst_file,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR)) == -1)
{
perror("open dst error");
return 1;
}
// 2. 循環(huán)從源文件中讀取數(shù)據(jù)寫(xiě)入到目的文件中
// 直到讀到源文件的尾部為止
// 2.1 read data from src_file
// 2.2 write data to dst_file
while((n = read(src_fd, buf, BUFFER_SIZE)) > 0)
{
write(dst_fd, buf, n);
}
// 3.close
close(src_fd);
close(dst_fd);
return 0;
}
// 練習(xí):
// 實(shí)現(xiàn)一個(gè)相對(duì)完整版的cp程序,要求能夠判斷出目標(biāo)文件是否存在。
// 如果存在,給出提示是否覆蓋。
// 思路:
// 1.打開(kāi)源文件
// 2.判斷目的文件是否存在
// 3.如果目的文件存在,提示是否覆蓋
// 4.如果選擇覆蓋,則以只寫(xiě)的方式打開(kāi)文件,并截短文件內(nèi)容(O_TRUNC)
// 5.如果選擇不覆蓋,則提醒輸入新的保存文件名,并已只寫(xiě)方式打開(kāi)
// 6.循環(huán)讀取源文件內(nèi)容,寫(xiě)入到目的文件中
// 7.關(guān)閉已打開(kāi)的文件
// 思考題1:能否關(guān)閉標(biāo)準(zhǔn)輸入文件、標(biāo)準(zhǔn)輸出文件、標(biāo)準(zhǔn)出錯(cuò)文件?
參考資料
劉老師上課資料及網(wǎng)上前輩資料
計(jì)算機(jī)操作系統(tǒng)教程:介紹現(xiàn)代操作系統(tǒng)原理及應(yīng)用